Bug 1395748 - Fix text selection shadow interaction. r=jrmuizel

Selections in gecko are used to hack in style changes to subsets of text frames.
Mostly this works fine because decorations don't care where they are, and
textRunFragments already exist to do style changes midFrame. However we mishandled
shadows because we were assuming they applied to the entire run, which isn't
the case when shadows are involved.

Applying shadows to everything was desirable because the way nsTextFrame is written,
it's difficult for us to associate the glyphs and decorations with a "range".
However the selections iterator provides a natural grouping, so we use that.

The result is that TextDrawTarget effectively becomes an array of what TextDrawTarget
used to be (now called SelectedTextRunFragment). Everything else is just fallout
of this change.

MozReview-Commit-ID: 5GWPruo6daW

--HG--
extra : rebase_source : 8e1c1d61e43151ee6651f8c6cfbcb0912262df56
This commit is contained in:
Alexis Beingessner 2017-09-12 16:50:44 -04:00
parent d83e387d9b
commit 39e87f69a6
2 changed files with 166 additions and 86 deletions

View File

@ -26,6 +26,31 @@ struct SelectionFragment {
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.
//
@ -66,6 +91,7 @@ public:
: mCurrentlyDrawing(Phase::eSelection)
{
mCurrentTarget = gfx::Factory::CreateDrawTarget(gfx::BackendType::SKIA, IntSize(1, 1), gfx::SurfaceFormat::B8G8R8A8);
SetSelectionIndex(0);
}
// Prevent this from being copied
@ -75,6 +101,17 @@ public:
// 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,
@ -104,14 +141,14 @@ public:
// 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;
@ -134,15 +171,18 @@ public:
}
}
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
@ -158,10 +198,10 @@ public:
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");
@ -208,34 +248,67 @@ public:
}
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()) {
return false;
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;

View File

@ -5161,29 +5161,7 @@ nsDisplayText::GetLayerState(nsDisplayListBuilder* aBuilder,
// 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;
}
@ -5237,38 +5215,42 @@ nsDisplayText::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder
// text, emphasisText, [grouped in one array]
// lineThrough
for (const mozilla::layout::SelectionFragment& selection:
mTextDrawer->GetSelections()) {
aBuilder.PushRect(selection.rect, wrClipRect, selection.color);
for (auto& part : mTextDrawer->GetParts()) {
if (part.selection) {
auto selection = part.selection.value();
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 (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: mTextDrawer->GetBeforeDecorations()) {
aBuilder.PushLine(wrClipRect, decoration);
}
for (const wr::Line& decoration : part.beforeDecorations) {
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;
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);
}
aManager->WrBridge()->PushGlyphs(aBuilder, text.glyphs, text.font,
color, aSc, boundsRect, clipRect);
}
for (const wr::Line& decoration: mTextDrawer->GetAfterDecorations()) {
aBuilder.PushLine(wrClipRect, decoration);
}
for (const wr::Line& decoration : part.afterDecorations) {
aBuilder.PushLine(wrClipRect, decoration);
}
for (size_t i = 0; i < mTextDrawer->GetShadows().Length(); ++i) {
aBuilder.PopTextShadow();
for (size_t i = 0; i < part.shadows.Length(); ++i) {
aBuilder.PopTextShadow();
}
}
return true;
@ -5298,19 +5280,26 @@ nsDisplayText::BuildLayer(nsDisplayListBuilder* aBuilder,
ScaledFont* font = nullptr;
nsTArray<GlyphArray> allGlyphs;
allGlyphs.SetCapacity(mTextDrawer->GetText().Length());
for (const mozilla::layout::TextRunFragment& text : mTextDrawer->GetText()) {
if (!font) {
font = text.font;
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;
}
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);
@ -6613,8 +6602,13 @@ nsTextFrame::PaintTextWithSelectionColors(
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);
@ -6636,8 +6630,8 @@ nsTextFrame::PaintTextWithSelectionColors(
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,
@ -6645,6 +6639,7 @@ nsTextFrame::PaintTextWithSelectionColors(
}
}
iterator.UpdateWithAdvance(advance);
++selectionIndex;
}
}
@ -6672,8 +6667,14 @@ nsTextFrame::PaintTextWithSelectionColors(
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);
@ -6712,6 +6713,7 @@ nsTextFrame::PaintTextWithSelectionColors(
DrawText(range, textBaselinePt, params);
advance += hyphenWidth;
iterator.UpdateWithAdvance(advance);
++selectionIndex;
}
return true;
}
@ -6787,8 +6789,12 @@ nsTextFrame::PaintTextSelectionDecorations(
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) {
@ -6808,6 +6814,7 @@ nsTextFrame::PaintTextSelectionDecorations(
verticalRun, decorationOffsetDir, kDecoration);
}
iterator.UpdateWithAdvance(advance);
++selectionIndex;
}
}