Bug 1040668 part 10 - Implement emphasis mark rendering. r=jfkthame

--HG--
extra : source : 1c53ccbaece3931ffe1da5610977e92fcce5f3f6
This commit is contained in:
Xidorn Quan 2015-11-28 11:56:33 +11:00
parent 72495f58ae
commit 6fdb9fbeaa
9 changed files with 286 additions and 5 deletions

View File

@ -48,6 +48,7 @@
#include "graphite2/Font.h"
#include <algorithm>
#include <cmath>
using namespace mozilla;
using namespace mozilla::gfx;
@ -1849,6 +1850,46 @@ gfxFont::DrawGlyphs(gfxShapedText *aShapedText,
return emittedGlyphs;
}
// This method is mostly parallel to DrawGlyphs.
void
gfxFont::DrawEmphasisMarks(gfxTextRun* aShapedText, gfxPoint* aPt,
uint32_t aOffset, uint32_t aCount,
const EmphasisMarkDrawParams& aParams)
{
gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
uint32_t markLength = aParams.mark->GetLength();
gfxFloat clusterStart = NAN;
bool shouldDrawEmphasisMark = false;
for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
if (aParams.spacing) {
inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
}
if (aShapedText->IsClusterStart(idx)) {
clusterStart = inlineCoord;
}
if (aShapedText->CharMayHaveEmphasisMark(idx)) {
shouldDrawEmphasisMark = true;
}
inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
if (shouldDrawEmphasisMark &&
(i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
MOZ_ASSERT(!std::isnan(clusterStart), "Should have cluster start");
gfxFloat clusterAdvance = inlineCoord - clusterStart;
// Move the coord backward to get the needed start point.
gfxFloat delta = (clusterAdvance + aParams.advance) / 2;
inlineCoord -= delta;
aParams.mark->Draw(aParams.context, *aPt, DrawMode::GLYPH_FILL,
0, markLength, nullptr, nullptr, nullptr);
inlineCoord += delta;
shouldDrawEmphasisMark = false;
}
if (aParams.spacing) {
inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
}
}
}
void
gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
gfxPoint *aPt, const TextRunDrawParams& aRunParams,

View File

@ -1307,6 +1307,7 @@ private:
class GlyphBufferAzure;
struct TextRunDrawParams;
struct FontDrawParams;
struct EmphasisMarkDrawParams;
class gfxFont {
@ -1601,6 +1602,16 @@ public:
gfxPoint *aPt, const TextRunDrawParams& aRunParams,
uint16_t aOrientation);
/**
* Draw the emphasis marks for the given text run. Its prerequisite
* and output are similiar to the method Draw().
* @param aPt the baseline origin of the emphasis marks.
* @param aParams some drawing parameters, see EmphasisMarkDrawParams.
*/
void DrawEmphasisMarks(gfxTextRun* aShapedText, gfxPoint* aPt,
uint32_t aOffset, uint32_t aCount,
const EmphasisMarkDrawParams& aParams);
/**
* Measure a run of characters. See gfxTextRun::Metrics.
* @param aTight if false, then return the union of the glyph extents
@ -2167,4 +2178,13 @@ struct FontDrawParams {
bool haveColorGlyphs;
};
struct EmphasisMarkDrawParams {
gfxContext* context;
gfxFont::Spacing* spacing;
gfxTextRun* mark;
gfxFloat advance;
gfxFloat direction;
bool isVertical;
};
#endif

View File

@ -1,4 +1,5 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set ts=4 et sw=4 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -665,6 +666,50 @@ gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode,
}
}
// This method is mostly parallel to Draw().
void
gfxTextRun::DrawEmphasisMarks(gfxContext *aContext, gfxTextRun* aMark,
gfxFloat aMarkAdvance, gfxPoint aPt,
uint32_t aStart, uint32_t aLength,
PropertyProvider* aProvider)
{
MOZ_ASSERT(aStart + aLength <= GetLength());
EmphasisMarkDrawParams params;
params.context = aContext;
params.mark = aMark;
params.advance = aMarkAdvance;
params.direction = GetDirection();
params.isVertical = IsVertical();
gfxFloat& inlineCoord = params.isVertical ? aPt.y : aPt.x;
gfxFloat direction = params.direction;
GlyphRunIterator iter(this, aStart, aLength);
while (iter.NextRun()) {
gfxFont* font = iter.GetGlyphRun()->mFont;
uint32_t start = iter.GetStringStart();
uint32_t end = iter.GetStringEnd();
uint32_t ligatureRunStart = start;
uint32_t ligatureRunEnd = end;
ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
inlineCoord += direction *
ComputePartialLigatureWidth(start, ligatureRunStart, aProvider);
nsAutoTArray<PropertyProvider::Spacing, 200> spacingBuffer;
bool haveSpacing = GetAdjustedSpacingArray(
ligatureRunStart, ligatureRunEnd, aProvider,
ligatureRunStart, ligatureRunEnd, &spacingBuffer);
params.spacing = haveSpacing ? spacingBuffer.Elements() : nullptr;
font->DrawEmphasisMarks(this, &aPt, ligatureRunStart,
ligatureRunEnd - ligatureRunStart, params);
inlineCoord += direction *
ComputePartialLigatureWidth(ligatureRunEnd, end, aProvider);
}
}
void
gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont,
uint32_t aStart, uint32_t aEnd,

View File

@ -251,6 +251,16 @@ public:
gfxFloat *aAdvanceWidth, gfxTextContextPaint *aContextPaint,
gfxTextRunDrawCallbacks *aCallbacks = nullptr);
/**
* Draws the emphasis marks for this text run. Uses only GetSpacing
* from aProvider. The provided point is the baseline origin of the
* line of emphasis marks.
*/
void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark,
gfxFloat aMarkAdvance, gfxPoint aPt,
uint32_t aStart, uint32_t aLength,
PropertyProvider* aProvider);
/**
* Computes the ReflowMetrics for a substring.
* Uses GetSpacing from aBreakProvider.

View File

@ -3285,8 +3285,11 @@ nsLineLayout::RelativePositionFrames(PerSpanData* psd, nsOverflowAreas& aOverflo
// (1) When PFD_RECOMPUTEOVERFLOW is set due to trimming
// (2) When there are text decorations, since we can't recompute the
// overflow area until Reflow and VerticalAlignLine have finished
// (3) When there are text emphasis marks, since the marks may be
// put further away if the text is inside ruby.
if (pfd->mRecomputeOverflow ||
frame->StyleContext()->HasTextDecorationLines()) {
frame->StyleContext()->HasTextDecorationLines() ||
frame->StyleText()->HasTextEmphasis()) {
nsTextFrame* f = static_cast<nsTextFrame*>(frame);
r = f->RecomputeOverflow(mBlockReflowState->frame);
}

View File

@ -5116,6 +5116,92 @@ GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
}
static already_AddRefed<nsFontMetrics>
GetFontMetricsOfEmphasisMarks(nsStyleContext* aStyleContext, float aInflation)
{
nsPresContext* pc = aStyleContext->PresContext();
WritingMode wm(aStyleContext);
gfxFont::Orientation orientation = wm.IsVertical() && !wm.IsSideways() ?
gfxFont::eVertical : gfxFont::eHorizontal;
const nsStyleFont* styleFont = aStyleContext->StyleFont();
nsFont font = styleFont->mFont;
font.size = NSToCoordRound(font.size * aInflation * 0.5f);
RefPtr<nsFontMetrics> fm;
pc->DeviceContext()->GetMetricsFor(font, styleFont->mLanguage,
styleFont->mExplicitLanguage,
orientation, pc->GetUserFontSet(),
pc->GetTextPerfMetrics(),
*getter_AddRefs(fm));
return fm.forget();
}
static gfxTextRun*
GenerateTextRunForEmphasisMarks(nsTextFrame* aFrame, nsFontMetrics* aFontMetrics,
WritingMode aWM, const nsStyleText* aStyleText)
{
const nsString& emphasisString = aStyleText->mTextEmphasisStyleString;
RefPtr<gfxContext> ctx = CreateReferenceThebesContext(aFrame);
auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
uint32_t flags = nsLayoutUtils::
GetTextRunOrientFlagsForStyle(aFrame->StyleContext());
if (flags == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED) {
// The emphasis marks should always be rendered upright per spec.
flags = gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
}
return aFontMetrics->GetThebesFontGroup()->
MakeTextRun<char16_t>(emphasisString.get(), emphasisString.Length(),
ctx, appUnitsPerDevUnit, flags, nullptr);
}
nsRect
nsTextFrame::UpdateTextEmphasis(WritingMode aWM, PropertyProvider& aProvider)
{
const nsStyleText* styleText = StyleText();
if (!styleText->HasTextEmphasis()) {
Properties().Delete(EmphasisMarkProperty());
return nsRect();
}
RefPtr<nsFontMetrics> fm =
GetFontMetricsOfEmphasisMarks(StyleContext(), GetFontSizeInflation());
EmphasisMarkInfo* info = new EmphasisMarkInfo;
info->textRun =
GenerateTextRunForEmphasisMarks(this, fm, aWM, styleText);
info->advance =
info->textRun->GetAdvanceWidth(0, info->textRun->GetLength(), nullptr);
// Calculate the baseline offset
LogicalSide side = styleText->TextEmphasisSide(aWM);
nsFontMetrics* baseFontMetrics = aProvider.GetFontMetrics();
LogicalSize frameSize = GetLogicalSize();
// The overflow rect is inflated in the inline direction by half
// advance of the emphasis mark on each side, so that even if a mark
// is drawn for a zero-width character, it won't be clipped.
LogicalRect overflowRect(aWM, -info->advance / 2,
/* BStart to be computed below */0,
frameSize.ISize(aWM) + info->advance,
fm->MaxAscent() + fm->MaxDescent());
// When the writing mode is vertical-lr the line is inverted, and thus
// the ascent and descent are swapped.
nscoord absOffset = (side == eLogicalSideBStart) != aWM.IsLineInverted() ?
baseFontMetrics->MaxAscent() + fm->MaxDescent() :
baseFontMetrics->MaxDescent() + fm->MaxAscent();
// XXX emphasis marks should be drawn outside ruby, see bug 1224013.
if (side == eLogicalSideBStart) {
info->baselineOffset = -absOffset;
overflowRect.BStart(aWM) = -overflowRect.BSize(aWM);
} else {
MOZ_ASSERT(side == eLogicalSideBEnd);
info->baselineOffset = absOffset;
overflowRect.BStart(aWM) = frameSize.BSize(aWM);
}
Properties().Set(EmphasisMarkProperty(), info);
return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
}
void
nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
nsIFrame* aBlock,
@ -5123,9 +5209,10 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
nsRect* aVisualOverflowRect,
bool aIncludeTextDecorations)
{
const WritingMode wm = GetWritingMode();
bool verticalRun = mTextRun->IsVertical();
bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
bool inverted = GetWritingMode().IsLineInverted();
bool inverted = wm.IsLineInverted();
if (IsFloatingFirstLetterChild()) {
// The underline/overline drawable area must be contained in the overflow
@ -5185,7 +5272,6 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
gfxWidth = measure / appUnitsPerDevUnit;
gfxFloat ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
const WritingMode wm = GetWritingMode();
if (wm.IsVerticalRL()) {
ascent = -ascent;
}
@ -5295,6 +5381,9 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
verticalRun ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
: nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
}
aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
UpdateTextEmphasis(wm, aProvider));
}
// Text-shadow overflows
@ -6107,6 +6196,36 @@ nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
return true;
}
void
nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
const gfxPoint& aTextBaselinePt,
uint32_t aOffset, uint32_t aLength,
PropertyProvider& aProvider)
{
auto info = static_cast<const EmphasisMarkInfo*>(
Properties().Get(EmphasisMarkProperty()));
if (!info) {
MOZ_ASSERT(!StyleText()->HasTextEmphasis());
return;
}
nscolor color = nsLayoutUtils::
GetColor(this, eCSSProperty_text_emphasis_color);
aContext->SetColor(Color::FromABGR(color));
gfxPoint pt(aTextBaselinePt);
if (!aWM.IsVertical()) {
pt.y += info->baselineOffset;
} else {
if (aWM.IsVerticalRL()) {
pt.x -= info->baselineOffset;
} else {
pt.x += info->baselineOffset;
}
}
mTextRun->DrawEmphasisMarks(aContext, info->textRun, info->advance,
pt, aOffset, aLength, &aProvider);
}
nscolor
nsTextFrame::GetCaretColorAt(int32_t aOffset)
{
@ -6609,6 +6728,9 @@ nsTextFrame::DrawTextRunAndDecorations(
DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
// Emphasis marks
DrawEmphasisMarks(aCtx, wm, aTextBaselinePt, aOffset, aLength, aProvider);
// Line-throughs
for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) {
const LineDecoration& dec = aDecorations.mStrikes[i];
@ -6655,7 +6777,8 @@ nsTextFrame::DrawText(
// Hide text decorations if we're currently hiding @font-face fallback text
const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
decorations.HasDecorationLines();
(decorations.HasDecorationLines() ||
StyleText()->HasTextEmphasis());
if (drawDecorations) {
DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength,
aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth,

View File

@ -440,6 +440,12 @@ public:
SelectionType aSelectionType,
DrawPathCallbacks* aCallbacks);
void DrawEmphasisMarks(gfxContext* aContext,
mozilla::WritingMode aWM,
const gfxPoint& aTextBaselinePt,
uint32_t aOffset, uint32_t aLength,
PropertyProvider& aProvider);
virtual nscolor GetCaretColorAt(int32_t aOffset) override;
int16_t GetSelectionStatus(int16_t* aSelectionFlags);
@ -585,6 +591,11 @@ protected:
nsRect* aVisualOverflowRect,
bool aIncludeTextDecorations);
// Update information of emphasis marks, and return the visial
// overflow rect of the emphasis marks.
nsRect UpdateTextEmphasis(mozilla::WritingMode aWM,
PropertyProvider& aProvider);
void PaintOneShadow(uint32_t aOffset,
uint32_t aLength,
nsCSSShadowItem* aShadowDetails,
@ -812,6 +823,14 @@ protected:
void ClearMetrics(nsHTMLReflowMetrics& aMetrics);
NS_DECLARE_FRAME_PROPERTY(JustificationAssignment, nullptr)
struct EmphasisMarkInfo
{
nsAutoPtr<gfxTextRun> textRun;
gfxFloat advance;
gfxFloat baselineOffset;
};
NS_DECLARE_FRAME_PROPERTY(EmphasisMarkProperty, DeleteValue<EmphasisMarkInfo>)
};
#endif

View File

@ -3726,6 +3726,24 @@ nsChangeHint nsStyleText::CalcDifference(const nsStyleText& aOther) const
return NS_STYLE_HINT_NONE;
}
LogicalSide
nsStyleText::TextEmphasisSide(WritingMode aWM) const
{
MOZ_ASSERT(
(!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT) !=
!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT)) &&
(!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER) !=
!(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER)));
Side side = aWM.IsVertical() ?
(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT
? eSideLeft : eSideRight) :
(mTextEmphasisPosition & NS_STYLE_TEXT_EMPHASIS_POSITION_OVER
? eSideTop : eSideBottom);
LogicalSide result = aWM.LogicalSideForPhysicalSide(side);
MOZ_ASSERT(IsBlock(result));
return result;
}
//-----------------------
// nsStyleUserInterface
//

View File

@ -1811,6 +1811,8 @@ struct nsStyleText {
inline bool NewlineIsSignificant(const nsTextFrame* aContextFrame) const;
inline bool WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const;
inline bool WordCanWrap(const nsIFrame* aContextFrame) const;
mozilla::LogicalSide TextEmphasisSide(mozilla::WritingMode aWM) const;
};
struct nsStyleImageOrientation {