mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
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:
parent
d83e387d9b
commit
39e87f69a6
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user