--- a/layout/generic/TextDrawTarget.h
+++ b/layout/generic/TextDrawTarget.h
@@ -21,16 +21,41 @@ struct TextRunFragment {
};
// Only webrender handles this, so we use webrender types
struct SelectionFragment {
wr::ColorF color;
wr::LayoutRect rect;
};
+// Selections are used in nsTextFrame to hack in sub-frame style changes.
+// Most notably text-shadows can be changed by selections, and so we need to
+// group all the glyphs and decorations attached to a shadow. We do this by
+// having shadows apply to an entire SelectedTextRunFragment, and creating
+// one for each "piece" of selection.
+//
+// For instance, this text:
+//
+// Hello [there] my name [is Mega]man
+// ^ ^
+// normal selection Ctrl+F highlight selection (yeah it's very overloaded)
+//
+// Would be broken up into 5 SelectedTextRunFragments
+//
+// ["Hello ", "there", " my name ", "is Mega", "man"]
+//
+// For almost all nsTextFrames, there will be only one SelectedTextRunFragment.
+struct SelectedTextRunFragment {
+ Maybe<SelectionFragment> selection;
+ nsTArray<wr::TextShadow> shadows;
+ nsTArray<TextRunFragment> text;
+ nsTArray<wr::Line> beforeDecorations;
+ nsTArray<wr::Line> afterDecorations;
+};
+
// This class is fake DrawTarget, used to intercept text draw calls, while
// also collecting up the other aspects of text natively.
//
// When using advanced-layers in nsDisplayText's constructor, we construct this
// and run the full painting algorithm with this as the DrawTarget. This is
// done to avoid having to massively refactor gecko's text painting code (which
// has lots of components shared between other rendering algorithms).
//
@@ -61,25 +86,37 @@ public:
enum class Phase : uint8_t {
eSelection, eUnderline, eOverline, eGlyphs, eEmphasisMarks, eLineThrough
};
explicit TextDrawTarget()
: mCurrentlyDrawing(Phase::eSelection)
{
mCurrentTarget = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8);
+ SetSelectionIndex(0);
}
// Prevent this from being copied
TextDrawTarget(const TextDrawTarget& src) = delete;
TextDrawTarget& operator=(const TextDrawTarget&) = delete;
// Change the phase of text we're drawing.
void StartDrawing(Phase aPhase) { mCurrentlyDrawing = aPhase; }
+ void SetSelectionIndex(size_t i) {
+ // i should only be accessed if i-1 has already been
+ MOZ_ASSERT(mParts.Length() <= i);
+
+ if (mParts.Length() == i){
+ mParts.AppendElement();
+ }
+
+ mCurrentPart = &mParts[i];
+ }
+
// This overload just stores the glyphs/font/color.
void
FillGlyphs(ScaledFont* aFont,
const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const DrawOptions& aOptions,
const GlyphRenderingOptions* aRenderingOptions) override
{
@@ -99,24 +136,24 @@ public:
if (mCurrentlyDrawing != Phase::eGlyphs &&
mCurrentlyDrawing != Phase::eEmphasisMarks) {
MOZ_CRASH("TextDrawTarget received glyphs in wrong phase");
}
// We need to push a new TextRunFragment whenever the font/color changes
// (usually this implies some font fallback from mixing languages/emoji)
TextRunFragment* fragment;
- if (mText.IsEmpty() ||
- mText.LastElement().font != aFont ||
- mText.LastElement().color != colorPat->mColor) {
- fragment = mText.AppendElement();
+ if (mCurrentPart->text.IsEmpty() ||
+ mCurrentPart->text.LastElement().font != aFont ||
+ mCurrentPart->text.LastElement().color != colorPat->mColor) {
+ fragment = mCurrentPart->text.AppendElement();
fragment->font = aFont;
fragment->color = colorPat->mColor;
} else {
- fragment = &mText.LastElement();
+ fragment = &mCurrentPart->text.LastElement();
}
nsTArray<Glyph>& glyphs = fragment->glyphs;
size_t oldLength = glyphs.Length();
glyphs.SetLength(oldLength + aBuffer.mNumGlyphs);
PodCopy(glyphs.Elements() + oldLength, aBuffer.mGlyphs, aBuffer.mNumGlyphs);
@@ -129,44 +166,47 @@ public:
0, 0);
for (size_t i = oldLength; i < oldLength + aBuffer.mNumGlyphs; ++i) {
auto position = &glyphs[i].mPosition;
*position = skew.TransformPoint(*position);
}
}
}
- void AppendShadow(const wr::TextShadow& aShadow) { mShadows.AppendElement(aShadow); }
+ void
+ AppendShadow(const wr::TextShadow& aShadow) {
+ mCurrentPart->shadows.AppendElement(aShadow);
+ }
void
- AppendSelection(const LayoutDeviceRect& aRect, const Color& aColor)
+ SetSelectionRect(const LayoutDeviceRect& aRect, const Color& aColor)
{
SelectionFragment frag;
frag.rect = wr::ToLayoutRect(aRect);
frag.color = wr::ToColorF(aColor);
- mSelections.AppendElement(frag);
+ mCurrentPart->selection = Some(frag);
}
void
AppendDecoration(const Point& aStart,
const Point& aEnd,
const float aThickness,
const bool aVertical,
const Color& aColor,
const uint8_t aStyle)
{
wr::Line* decoration;
switch (mCurrentlyDrawing) {
case Phase::eUnderline:
case Phase::eOverline:
- decoration = mBeforeDecorations.AppendElement();
+ decoration = mCurrentPart->beforeDecorations.AppendElement();
break;
case Phase::eLineThrough:
- decoration = mAfterDecorations.AppendElement();
+ decoration = mCurrentPart->afterDecorations.AppendElement();
break;
default:
MOZ_CRASH("TextDrawTarget received Decoration in wrong phase");
}
// This function is basically designed to slide into the decoration drawing
// code of nsCSSRendering with minimum disruption, to minimize the
// chances of implementation drift. As such, it mostly looks like a call
@@ -203,44 +243,77 @@ public:
case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
default:
MOZ_CRASH("TextDrawTarget received unsupported line style");
}
}
- const nsTArray<wr::TextShadow>& GetShadows() { return mShadows; }
- const nsTArray<TextRunFragment>& GetText() { return mText; }
- const nsTArray<SelectionFragment>& GetSelections() { return mSelections; }
- const nsTArray<wr::Line>& GetBeforeDecorations() { return mBeforeDecorations; }
- const nsTArray<wr::Line>& GetAfterDecorations() { return mAfterDecorations; }
+ const nsTArray<SelectedTextRunFragment>& GetParts() { return mParts; }
bool
CanSerializeFonts()
{
- for (const TextRunFragment& frag : GetText()) {
- if (!frag.font->CanSerialize()) {
+ for (const SelectedTextRunFragment& part : GetParts()) {
+ for (const TextRunFragment& frag : part.text) {
+ if (!frag.font->CanSerialize()) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ // TextLayers don't support very complicated text right now. This checks
+ // if any of the problem cases exist.
+ bool
+ ContentsAreSimple()
+ {
+
+ ScaledFont* font = nullptr;
+
+ for (const SelectedTextRunFragment& part : GetParts()) {
+ // Can't handle shadows, selections, or decorations
+ if (part.shadows.Length() > 0 ||
+ part.beforeDecorations.Length() > 0 ||
+ part.afterDecorations.Length() > 0 ||
+ part.selection.isSome()) {
return false;
}
+
+ // Must only have one font (multiple colors is fine)
+ for (const mozilla::layout::TextRunFragment& text : part.text) {
+ if (!font) {
+ font = text.font;
+ }
+ if (font != text.font) {
+ return false;
+ }
+ }
}
+
+ // Must have an actual font (i.e. actual text)
+ if (!font) {
+ return false;
+ }
+
return true;
}
private:
// The part of the text we're currently drawing (glyphs, underlines, etc.)
Phase mCurrentlyDrawing;
- // Properties of the whole text
- nsTArray<wr::TextShadow> mShadows;
- nsTArray<TextRunFragment> mText;
- nsTArray<SelectionFragment> mSelections;
- nsTArray<wr::Line> mBeforeDecorations;
- nsTArray<wr::Line> mAfterDecorations;
+ // Which chunk of mParts is actively being populated
+ SelectedTextRunFragment* mCurrentPart;
+
+ // Chunks of the text, grouped by selection
+ nsTArray<SelectedTextRunFragment> mParts;
// A dummy to handle parts of the DrawTarget impl we don't care for
RefPtr<DrawTarget> mCurrentTarget;
// The rest of this is dummy implementations of DrawTarget's API
public:
DrawTargetType GetType() const override {
return mCurrentTarget->GetType();
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5156,39 +5156,17 @@ nsDisplayText::GetLayerState(nsDisplayLi
// If we're using the webrender backend, then we're good to go!
if (aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
return mozilla::LAYER_ACTIVE;
}
// If we're using the TextLayer backend, then we need to make sure
// the input is plain enough for it to handle
-
- // Can't handle shadows, selections, or decorations
- if (mTextDrawer->GetShadows().Length() > 0 ||
- mTextDrawer->GetSelections().Length() > 0 ||
- mTextDrawer->GetBeforeDecorations().Length() > 0 ||
- mTextDrawer->GetAfterDecorations().Length() > 0) {
- return mozilla::LAYER_NONE;
- }
-
- // Must only have one font (multiple colors is fine)
- ScaledFont* font = nullptr;
-
- for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
- if (!font) {
- font = text.font;
- }
- if (font != text.font) {
- return mozilla::LAYER_NONE;
- }
- }
-
- // Must have an actual font (i.e. actual text)
- if (!font) {
+ if (!mTextDrawer->ContentsAreSimple()) {
return mozilla::LAYER_NONE;
}
return mozilla::LAYER_ACTIVE;
}
void
nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
@@ -5232,48 +5210,52 @@ nsDisplayText::CreateWebRenderCommands(m
wr::LayoutRect wrClipRect = aSc.ToRelativeLayoutRect(clipRect); // wr::ToLayoutRect(clipRect);
wr::LayoutRect wrBoundsRect = aSc.ToRelativeLayoutRect(boundsRect); //wr::ToLayoutRect(boundsRect);
// Drawing order: selections, shadows,
// underline, overline, [grouped in one array]
// text, emphasisText, [grouped in one array]
// lineThrough
- for (const mozilla::layout::SelectionFragment& selection:
- mTextDrawer->GetSelections()) {
- aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
- }
-
- // WR takes the shadows in CSS-order (reverse of rendering order),
- // because the drawing of a shadow actually occurs when it's popped.
- for (const wr::TextShadow& shadow : mTextDrawer->GetShadows()) {
- aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, shadow);
- }
-
- for (const wr::Line& decoration: mTextDrawer->GetBeforeDecorations()) {
- aBuilder.PushLine(wrClipRect, decoration);
- }
-
- for (const mozilla::layout::TextRunFragment& text: mTextDrawer->GetText()) {
- // mOpacity is set after we do our analysis, so we need to apply it here.
- // mOpacity is only non-trivial when we have "pure" text, so we don't
- // ever need to apply it to shadows or decorations.
- auto color = text.color;
- color.a *= mOpacity;
-
- aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
- color, aSc, boundsRect, clipRect);
- }
-
- for (const wr::Line& decoration: mTextDrawer->GetAfterDecorations()) {
- aBuilder.PushLine(wrClipRect, decoration);
- }
-
- for (size_t i = 0; i < mTextDrawer->GetShadows().Length(); ++i) {
- aBuilder.PopTextShadow();
+ for (auto& part : mTextDrawer->GetParts()) {
+ if (part.selection) {
+ auto selection = part.selection.value();
+ aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
+ }
+ }
+
+ for (auto& part : mTextDrawer->GetParts()) {
+ // WR takes the shadows in CSS-order (reverse of rendering order),
+ // because the drawing of a shadow actually occurs when it's popped.
+ for (const wr::TextShadow& shadow : part.shadows) {
+ aBuilder.PushTextShadow(wrBoundsRect, wrClipRect, shadow);
+ }
+
+ for (const wr::Line& decoration : part.beforeDecorations) {
+ aBuilder.PushLine(wrClipRect, decoration);
+ }
+
+ for (const mozilla::layout::TextRunFragment& text : part.text) {
+ // mOpacity is set after we do our analysis, so we need to apply it here.
+ // mOpacity is only non-trivial when we have "pure" text, so we don't
+ // ever need to apply it to shadows or decorations.
+ auto color = text.color;
+ color.a *= mOpacity;
+
+ aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
+ color, aSc, boundsRect, clipRect);
+ }
+
+ for (const wr::Line& decoration : part.afterDecorations) {
+ aBuilder.PushLine(wrClipRect, decoration);
+ }
+
+ for (size_t i = 0; i < part.shadows.Length(); ++i) {
+ aBuilder.PopTextShadow();
+ }
}
return true;
}
already_AddRefed<layers::Layer>
nsDisplayText::BuildLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
@@ -5293,29 +5275,36 @@ nsDisplayText::BuildLayer(nsDisplayListB
layer = aManager->CreateTextLayer();
}
// GetLayerState has guaranteed to us that we have exactly one font
// so this will be overwritten by the time we use it.
ScaledFont* font = nullptr;
nsTArray<GlyphArray> allGlyphs;
- allGlyphs.SetCapacity(mTextDrawer->GetText().Length());
- for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
- if (!font) {
- font = text.font;
- }
-
- GlyphArray* glyphs = allGlyphs.AppendElement();
- glyphs->glyphs() = text.glyphs;
-
- // Apply folded alpha (only applies to glyphs)
- auto color = text.color;
- color.a *= mOpacity;
- glyphs->color() = color;
+ size_t totalLength = 0;
+ for (auto& part : mTextDrawer->GetParts()) {
+ totalLength += part.text.Length();
+ }
+ allGlyphs.SetCapacity(totalLength);
+
+ for (auto& part : mTextDrawer->GetParts()) {
+ for (const mozilla::layout::TextRunFragment& text : part.text) {
+ if (!font) {
+ font = text.font;
+ }
+
+ GlyphArray* glyphs = allGlyphs.AppendElement();
+ glyphs->glyphs() = text.glyphs;
+
+ // Apply folded alpha (only applies to glyphs)
+ auto color = text.color;
+ color.a *= mOpacity;
+ glyphs->color() = color;
+ }
}
MOZ_ASSERT(font);
layer->SetGlyphs(Move(allGlyphs));
layer->SetScaledFont(font);
auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
@@ -6614,18 +6603,23 @@ nsTextFrame::PaintTextWithSelectionColor
TextRangeStyle rangeStyle;
// Draw background colors
if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
int32_t appUnitsPerDevPixel =
aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
SelectionIterator iterator(prevailingSelections, contentRange,
*aParams.provider, mTextRun, startIOffset);
SelectionType selectionType;
+ size_t selectionIndex = 0;
while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
&selectionType, &rangeStyle)) {
+ if (aParams.textDrawer) {
+ aParams.textDrawer->SetSelectionIndex(selectionIndex);
+ }
+
nscolor foreground, background;
GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
rangeStyle, &foreground, &background);
// Draw background color
gfxFloat advance = hyphenWidth +
mTextRun->GetAdvanceWidth(range, aParams.provider);
if (NS_GET_A(background) > 0) {
nsRect bgRect;
@@ -6637,25 +6631,26 @@ nsTextFrame::PaintTextWithSelectionColor
bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y,
advance, GetSize().height);
}
LayoutDeviceRect selectionRect =
LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
if (aParams.textDrawer) {
- aParams.textDrawer->AppendSelection(selectionRect,
- ToDeviceColor(background));
+ aParams.textDrawer->SetSelectionRect(selectionRect,
+ ToDeviceColor(background));
} else {
PaintSelectionBackground(
*aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
selectionRect, aParams.callbacks);
}
}
iterator.UpdateWithAdvance(advance);
+ ++selectionIndex;
}
}
if (aParams.IsPaintBGColor()) {
return true;
}
gfxFloat advance;
@@ -6673,18 +6668,24 @@ nsTextFrame::PaintTextWithSelectionColor
shadowParams.provider = aParams.provider;
shadowParams.clipEdges = &aClipEdges;
// Draw text
const nsStyleText* textStyle = StyleText();
SelectionIterator iterator(prevailingSelections, contentRange,
*aParams.provider, mTextRun, startIOffset);
SelectionType selectionType;
+
+ size_t selectionIndex = 0;
while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
&selectionType, &rangeStyle)) {
+ if (aParams.textDrawer) {
+ aParams.textDrawer->SetSelectionIndex(selectionIndex);
+ }
+
nscolor foreground, background;
if (aParams.IsGenerateTextMask()) {
foreground = NS_RGBA(0, 0, 0, 255);
} else {
GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
rangeStyle, &foreground, &background);
}
@@ -6713,16 +6714,17 @@ nsTextFrame::PaintTextWithSelectionColor
// Draw text segment
params.textColor = foreground;
params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
params.drawSoftHyphen = hyphenWidth > 0;
DrawText(range, textBaselinePt, params);
advance += hyphenWidth;
iterator.UpdateWithAdvance(advance);
+ ++selectionIndex;
}
return true;
}
void
nsTextFrame::PaintTextSelectionDecorations(
const PaintTextSelectionParams& aParams,
const UniquePtr<SelectionDetails>& aDetails,
@@ -6788,18 +6790,22 @@ nsTextFrame::PaintTextSelectionDecoratio
if (verticalRun) {
pt.x = (aParams.textBaselinePt.x - mAscent) / app;
} else {
pt.y = (aParams.textBaselinePt.y - mAscent) / app;
}
gfxFloat decorationOffsetDir = mTextRun->IsSidewaysLeft() ? -1.0 : 1.0;
SelectionType nextSelectionType;
TextRangeStyle selectedStyle;
+ size_t selectionIndex = 0;
while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
&nextSelectionType, &selectedStyle)) {
+ if (aParams.textDrawer) {
+ aParams.textDrawer->SetSelectionIndex(selectionIndex);
+ }
gfxFloat advance = hyphenWidth +
mTextRun->GetAdvanceWidth(range, aParams.provider);
if (nextSelectionType == aSelectionType) {
if (verticalRun) {
pt.y = (aParams.framePt.y + iOffset -
(mTextRun->IsInlineReversed() ? advance : 0)) / app;
} else {
pt.x = (aParams.framePt.x + iOffset -
@@ -6809,16 +6815,17 @@ nsTextFrame::PaintTextSelectionDecoratio
gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
DrawSelectionDecorations(
aParams.context, aParams.dirtyRect, aSelectionType,
*aParams.textPaintStyle, selectedStyle, pt, xInFrame,
width, mAscent / app, decorationMetrics, aParams.callbacks,
verticalRun, decorationOffsetDir, kDecoration);
}
iterator.UpdateWithAdvance(advance);
+ ++selectionIndex;
}
}
bool
nsTextFrame::PaintTextWithSelection(
const PaintTextSelectionParams& aParams,
const nsCharClipDisplayItem::ClipEdges& aClipEdges)
{