Skip to content
Permalink
Browse files
[Painting] Add support for decorating box based text box painting (ho…
…rizontal mode only)

https://bugs.webkit.org/show_bug.cgi?id=243766

Reviewed by Antti Koivisto.

Collect decorating ancestor inline boxes use their style/geometry to call TextDecorationPainter::paintBackgroundDecorations().

(The fuzzy change is due to multiple underlines getting painted directly on top of each other on subpixel positions between glyphs with descenders (coming from vertically overlapping decorating boxes e.g. <div style="text-decoration: underline"><font>ggg</font></div>)

* Source/WebCore/rendering/TextBoxPainter.cpp:
(WebCore::TextBoxPainter<TextBoxPath>::TextBoxPainter):
(WebCore::TextBoxPainter<TextBoxPath>::paintForegroundAndDecorations):
(WebCore::computedTextDecorationType):
(WebCore::TextBoxPainter<TextBoxPath>::collectDecoratingBoxesForTextBox):
(WebCore::TextBoxPainter<TextBoxPath>::paintBackgroundDecorations):
(WebCore::TextBoxPainter<TextBoxPath>::paintForegroundDecorations):
* Source/WebCore/rendering/TextBoxPainter.h:
* Source/WebCore/rendering/TextDecorationPainter.cpp:
(WebCore::TextDecorationPainter::paintBackgroundDecorations):
* Source/WebCore/style/InlineTextBoxStyle.cpp:
(WebCore::boxOffsetFromBottomMost):
(WebCore::textRunOffsetFromBottomMost):
(WebCore::underlineOffsetForTextBoxPainting):
* Source/WebCore/style/InlineTextBoxStyle.h:

Canonical link: https://commits.webkit.org/253406@main
  • Loading branch information
alanbaradlay committed Aug 14, 2022
1 parent 165f03c commit 519c36a6ca3c9d4a19cf296afb10185ec0943987
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 60 deletions.
@@ -1,4 +1,5 @@
<!DOCTYPE html>
<meta name="fuzzy" content="maxDifference=58-64; totalPixels=3-7" />
<style>
.outer {
text-decoration: underline;
@@ -1,4 +1,5 @@
<!DOCTYPE html>
<meta name="fuzzy" content="maxDifference=59-64; totalPixels=5-10" />
<html>
<head>
<style>
@@ -1,4 +1,5 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<meta name="fuzzy" content="maxDifference=61-64; totalPixels=6-19" />
<link rel=match href=001-ref.html>
<title>The font element text decoration color quirk, 001, almost standards mode</title>
<style>[id] > * { color:fuchsia }</style>
@@ -1,3 +1,4 @@
<meta name="fuzzy" content="maxDifference=61-64; totalPixels=6-19" />
<link rel=match href=001-ref.html>
<title>The font element text decoration color quirk, 001, quirks mode</title>
<style>[id] > * { color:fuchsia }</style>
@@ -1,4 +1,5 @@
<!doctype html>
<meta name="fuzzy" content="maxDifference=61-64; totalPixels=6-19" />
<link rel=match href=001-ref.html>
<title>The font element text decoration color quirk, 001, standards mode</title>
<style>[id] > * { color:fuchsia }</style>
@@ -30,6 +30,7 @@
#include "Editor.h"
#include "EventRegion.h"
#include "GraphicsContext.h"
#include "HTMLAnchorElement.h"
#include "InlineIteratorLineBox.h"
#include "InlineTextBoxStyle.h"
#include "LegacyInlineTextBox.h"
@@ -70,6 +71,7 @@ TextBoxPainter<TextBoxPath>::TextBoxPainter(TextBoxPath&& textBox, PaintInfo& pa
, m_paintTextRun(m_textBox.createTextRun(InlineIterator::CreateTextRunMode::Painting))
, m_paintInfo(paintInfo)
, m_selectableRange(m_textBox.selectableRange())
, m_paintOffset(paintOffset)
, m_paintRect(computePaintRect(paintOffset))
, m_isFirstLine(m_textBox.isFirstLine())
, m_isCombinedText(is<RenderCombineText>(m_renderer) && downcast<RenderCombineText>(m_renderer).isCombined())
@@ -282,17 +284,17 @@ void TextBoxPainter<TextBoxPath>::paintForegroundAndDecorations()
unsigned endOffset = markedText.endOffset;
if (startOffset < endOffset) {
// Avoid measuring the text when the entire line box is selected as an optimization.
FloatRect snappedSelectionRect = m_paintRect;
auto snappedPaintRect = m_paintRect;
if (startOffset || endOffset != m_paintTextRun.length()) {
LayoutRect selectionRect = { m_paintRect.x(), m_paintRect.y(), m_paintRect.width(), m_paintRect.height() };
fontCascade().adjustSelectionRectForText(m_paintTextRun, selectionRect, startOffset, endOffset);
snappedSelectionRect = snapRectToDevicePixelsWithWritingDirection(selectionRect, m_document.deviceScaleFactor(), m_paintTextRun.ltr());
snappedPaintRect = snapRectToDevicePixelsWithWritingDirection(selectionRect, m_document.deviceScaleFactor(), m_paintTextRun.ltr());
}

TextDecorationPainter decorationPainter = createDecorationPainter(markedText, textDecorationSelectionClipOutRect);
paintBackgroundDecorations(decorationPainter, markedText, snappedSelectionRect);
auto decorationPainter = createDecorationPainter(markedText, textDecorationSelectionClipOutRect);
paintBackgroundDecorations(decorationPainter, markedText, snappedPaintRect);
paintForeground(markedText);
paintForegroundDecorations(decorationPainter, markedText, snappedSelectionRect);
paintForegroundDecorations(decorationPainter, markedText, snappedPaintRect);
}
}
} else {
@@ -455,59 +457,119 @@ static inline float computedLinethroughCenter(const RenderStyle& styleToUse, flo
return center - textDecorationThickness / 2;
}

static inline OptionSet<TextDecorationLine> computedTextDecorationType(const RenderStyle& style, const TextDecorationPainter::Styles& textDecorationStyles)
{
auto textDecorations = style.textDecorationsInEffect();
textDecorations.add(TextDecorationPainter::textDecorationsInEffectForStyle(textDecorationStyles));
return textDecorations;
}

static inline bool isDecoratingBoxForBackground(const InlineIterator::InlineBox& inlineBox, const RenderStyle& styleToUse)
{
if (auto* element = inlineBox.renderer().element(); element && (is<HTMLAnchorElement>(*element) || element->hasTagName(HTMLNames::fontTag))) {
// <font> and <a> are always considered decorating boxes.
return true;
}
return styleToUse.textDecorationLine().containsAny({ TextDecorationLine::Underline, TextDecorationLine::Overline })
|| (inlineBox.isRootInlineBox() && styleToUse.textDecorationsInEffect().containsAny({ TextDecorationLine::Underline, TextDecorationLine::Overline }));
}

template<typename TextBoxPath>
void TextBoxPainter<TextBoxPath>::paintBackgroundDecorations(TextDecorationPainter& decorationPainter, const StyledMarkedText& markedText, const FloatRect& snappedSelectionRect)
void TextBoxPainter<TextBoxPath>::collectDecoratingBoxesForTextBox(DecoratingBoxList& decoratingBoxList, const InlineIterator::TextBoxIterator& textBox, FloatPoint textBoxLocation, const TextDecorationPainter::Styles& overrideDecorationStyle)
{
auto ancestorInlineBox = textBox->parentInlineBox();
// FIXME: Vertical writing mode needs some coordinate space transformation for parent inline boxes as we rotate the content with m_paintRect (see ::paint)
if (ancestorInlineBox->isRootInlineBox() || !textBox->isHorizontal()) {
decoratingBoxList.append({
ancestorInlineBox,
m_isFirstLine ? m_renderer.firstLineStyle() : m_renderer.style(),
overrideDecorationStyle,
textBoxLocation
});
return;
}

enum UseOverriderDecorationStyle : bool { Yes, No };
auto appendIfIsDecoratingBoxForBackground = [&] (auto& inlineBox, auto useOverriderDecorationStyle) {
auto& style = m_isFirstLine ? inlineBox->renderer().firstLineStyle() : inlineBox->renderer().style();

auto computedDecorationStyle = [&] {
return TextDecorationPainter::stylesForRenderer(inlineBox->renderer(), style.textDecorationsInEffect(), m_isFirstLine);
};
if (!isDecoratingBoxForBackground(*inlineBox, style)) {
// Some cases even non-decoration boxes may have some decoration pieces coming from the marked text (e.g. highlight).
if (useOverriderDecorationStyle == UseOverriderDecorationStyle::No || overrideDecorationStyle == computedDecorationStyle())
return;
}

decoratingBoxList.append({
inlineBox,
style,
useOverriderDecorationStyle == UseOverriderDecorationStyle::Yes ? overrideDecorationStyle : computedDecorationStyle(),
{ textBoxLocation.x(), m_paintOffset.y() + inlineBox->logicalTop() }
});
};

// FIXME: Figure out if the decoration styles coming from the styled marked text should be used only on the closest inline box (direct parent).
appendIfIsDecoratingBoxForBackground(ancestorInlineBox, UseOverriderDecorationStyle::Yes);
while (!ancestorInlineBox->isRootInlineBox()) {
ancestorInlineBox = ancestorInlineBox->parentInlineBox();
appendIfIsDecoratingBoxForBackground(ancestorInlineBox, UseOverriderDecorationStyle::No);
}
}

template<typename TextBoxPath>
void TextBoxPainter<TextBoxPath>::paintBackgroundDecorations(TextDecorationPainter& decorationPainter, const StyledMarkedText& markedText, const FloatRect& textBoxPaintRect)
{
if (m_isCombinedText)
m_paintInfo.context().concatCTM(rotation(m_paintRect, Clockwise));

auto textRun = m_paintTextRun.subRun(markedText.startOffset, markedText.endOffset - markedText.startOffset);
auto& styleToUse = m_isFirstLine ? m_renderer.firstLineStyle() : m_renderer.style();
const auto& textDecorationStyles = markedText.style.textDecorationStyles;
auto computedTextDecorationType = [&] {
auto textDecorations = styleToUse.textDecorationsInEffect();
textDecorations.add(TextDecorationPainter::textDecorationsInEffectForStyle(textDecorationStyles));
return textDecorations;
}();
auto computedBackgroundDecorationGeometry = [&] {
auto deviceScaleFactor = m_document.deviceScaleFactor();

auto textDecorationThickness = computedTextDecorationThickness(styleToUse, deviceScaleFactor);
auto underlineOffset = [&] {
if (!computedTextDecorationType.contains(TextDecorationLine::Underline))
return 0.f;
auto baseOffset = underlineOffsetForTextBoxPainting(styleToUse, makeIterator());
auto wavyOffset = textDecorationStyles.underline.decorationStyle == TextDecorationStyle::Wavy ? wavyOffsetFromDecoration() : 0.f;
return baseOffset + wavyOffset;
};
auto autoTextDecorationThickness = computedAutoTextDecorationThickness(styleToUse, deviceScaleFactor);
auto overlineOffset = [&] {
if (!computedTextDecorationType.contains(TextDecorationLine::Overline))
return 0.f;
return autoTextDecorationThickness - textDecorationThickness - (textDecorationStyles.overline.decorationStyle == TextDecorationStyle::Wavy ? wavyOffsetFromDecoration() : 0.f);
};

return TextDecorationPainter::BackgroundDecorationGeometry {
textOriginFromPaintRect(snappedSelectionRect),
snappedSelectionRect.location(),
snappedSelectionRect.width(),
textDecorationThickness,
underlineOffset(),
overlineOffset(),
computedLinethroughCenter(styleToUse, textDecorationThickness, autoTextDecorationThickness),
styleToUse.metricsOfPrimaryFont().ascent() + 2.f,
wavyStrokeParameters(styleToUse.computedFontPixelSize())
auto textBox = makeIterator();
auto decoratingBoxList = DecoratingBoxList { };
collectDecoratingBoxesForTextBox(decoratingBoxList, textBox, textBoxPaintRect.location(), markedText.style.textDecorationStyles);

for (auto& decoratingBox : makeReversedRange(decoratingBoxList)) {
auto computedTextDecorationType = WebCore::computedTextDecorationType(decoratingBox.style, decoratingBox.textDecorationStyles);
auto computedBackgroundDecorationGeometry = [&] {
auto textDecorationThickness = computedTextDecorationThickness(decoratingBox.style, m_document.deviceScaleFactor());
auto underlineOffset = [&] {
if (!computedTextDecorationType.contains(TextDecorationLine::Underline))
return 0.f;
auto baseOffset = underlineOffsetForTextBoxPainting(*decoratingBox.inlineBox, decoratingBox.style);
auto wavyOffset = decoratingBox.textDecorationStyles.underline.decorationStyle == TextDecorationStyle::Wavy ? wavyOffsetFromDecoration() : 0.f;
return baseOffset + wavyOffset;
};
auto autoTextDecorationThickness = computedAutoTextDecorationThickness(decoratingBox.style, m_document.deviceScaleFactor());
auto overlineOffset = [&] {
if (!computedTextDecorationType.contains(TextDecorationLine::Overline))
return 0.f;
return autoTextDecorationThickness - textDecorationThickness - (decoratingBox.textDecorationStyles.overline.decorationStyle == TextDecorationStyle::Wavy ? wavyOffsetFromDecoration() : 0.f);
};

return TextDecorationPainter::BackgroundDecorationGeometry {
textOriginFromPaintRect(textBoxPaintRect),
decoratingBox.location,
textBoxPaintRect.width(),
textDecorationThickness,
underlineOffset(),
overlineOffset(),
computedLinethroughCenter(decoratingBox.style, textDecorationThickness, autoTextDecorationThickness),
decoratingBox.style.metricsOfPrimaryFont().ascent() + 2.f,
wavyStrokeParameters(decoratingBox.style.computedFontPixelSize())
};
};
};

decorationPainter.paintBackgroundDecorations(textRun, computedBackgroundDecorationGeometry(), computedTextDecorationType, textDecorationStyles);
decorationPainter.paintBackgroundDecorations(textRun, computedBackgroundDecorationGeometry(), computedTextDecorationType, decoratingBox.textDecorationStyles);
}

if (m_isCombinedText)
m_paintInfo.context().concatCTM(rotation(m_paintRect, Counterclockwise));
}

template<typename TextBoxPath>
void TextBoxPainter<TextBoxPath>::paintForegroundDecorations(TextDecorationPainter& decorationPainter, const StyledMarkedText& markedText, const FloatRect& snappedSelectionRect)
void TextBoxPainter<TextBoxPath>::paintForegroundDecorations(TextDecorationPainter& decorationPainter, const StyledMarkedText& markedText, const FloatRect& textBoxPaintRect)
{
auto& styleToUse = m_isFirstLine ? m_renderer.firstLineStyle() : m_renderer.style();
auto computedTextDecorationType = [&] {
@@ -525,8 +587,8 @@ void TextBoxPainter<TextBoxPath>::paintForegroundDecorations(TextDecorationPaint
auto deviceScaleFactor = m_document.deviceScaleFactor();
auto textDecorationThickness = computedTextDecorationThickness(styleToUse, deviceScaleFactor);
auto linethroughCenter = computedLinethroughCenter(styleToUse, textDecorationThickness, computedAutoTextDecorationThickness(styleToUse, deviceScaleFactor));
decorationPainter.paintForegroundDecorations({ snappedSelectionRect.location()
, snappedSelectionRect.width()
decorationPainter.paintForegroundDecorations({ textBoxPaintRect.location()
, textBoxPaintRect.width()
, textDecorationThickness
, linethroughCenter
, wavyStrokeParameters(styleToUse.computedFontPixelSize()) }, markedText.style.textDecorationStyles);
@@ -25,9 +25,11 @@
#pragma once

#include "FloatRect.h"
#include "InlineIteratorInlineBox.h"
#include "InlineIteratorTextBox.h"
#include "RenderObject.h"
#include "TextBoxSelectableRange.h"
#include "TextDecorationPainter.h"
#include "TextRun.h"

namespace WebCore {
@@ -39,7 +41,6 @@ class RenderCombineText;
class RenderStyle;
class RenderText;
class ShadowData;
class TextDecorationPainter;
struct CompositionUnderline;
struct MarkedText;
struct PaintInfo;
@@ -80,6 +81,15 @@ class TextBoxPainter {
const FontCascade& fontCascade() const;
FloatPoint textOriginFromPaintRect(const FloatRect&) const;

struct DecoratingBox {
InlineIterator::InlineBoxIterator inlineBox;
const RenderStyle& style;
TextDecorationPainter::Styles textDecorationStyles;
FloatPoint location;
};
using DecoratingBoxList = Vector<DecoratingBox>;
void collectDecoratingBoxesForTextBox(DecoratingBoxList&, const InlineIterator::TextBoxIterator&, FloatPoint textBoxLocation, const TextDecorationPainter::Styles&);

const ShadowData* debugTextShadow() const;

const TextBoxPath m_textBox;
@@ -90,6 +100,7 @@ class TextBoxPainter {
const TextRun m_paintTextRun;
PaintInfo& m_paintInfo;
const TextBoxSelectableRange m_selectableRange;
const LayoutPoint m_paintOffset;
const FloatRect m_paintRect;
const bool m_isFirstLine;
const bool m_isCombinedText;
@@ -29,6 +29,7 @@
#include "FontCascade.h"
#include "HTMLAnchorElement.h"
#include "HTMLNames.h"
#include "InlineIteratorInlineBox.h"
#include "InlineIteratorTextBox.h"
#include "LegacyInlineTextBox.h"
#include "LegacyRootInlineBox.h"
@@ -118,18 +119,22 @@ static const RenderElement* enclosingRendererWithTextDecoration(const RenderText
return nullptr;
}

static float boxOffsetFromBottomMost(const InlineIterator::LineBoxIterator& lineBox, const RenderElement& decoratingInlineBoxRenderer, float boxLogicalTop, float boxLogicalBottom)
{
if (decoratingInlineBoxRenderer.style().isFlippedLinesWritingMode())
return boxLogicalTop - minLogicalTopForTextDecorationLineUnder(lineBox, boxLogicalTop, decoratingInlineBoxRenderer);
return maxLogicalBottomForTextDecorationLineUnder(lineBox, boxLogicalBottom, decoratingInlineBoxRenderer) - boxLogicalBottom;
}

static float textRunOffsetFromBottomMost(const InlineIterator::LineBoxIterator& lineBox, const RenderText& renderer, float textBoxLogicalTop, float textBoxLogicalBottom)
{
auto* decoratingBoxRendererForUnderline = enclosingRendererWithTextDecoration(renderer);
if (!decoratingBoxRendererForUnderline)
return 0.f;

if (renderer.style().isFlippedLinesWritingMode())
return textBoxLogicalTop - minLogicalTopForTextDecorationLineUnder(lineBox, textBoxLogicalTop, *decoratingBoxRendererForUnderline);
return maxLogicalBottomForTextDecorationLineUnder(lineBox, textBoxLogicalBottom, *decoratingBoxRendererForUnderline) - textBoxLogicalBottom;
return boxOffsetFromBottomMost(lineBox, *decoratingBoxRendererForUnderline, textBoxLogicalTop, textBoxLogicalBottom);
}


static inline float defaultGap(const RenderStyle& style)
{
const float textDecorationBaseFontSize = 16.f;
@@ -295,14 +300,14 @@ GlyphOverflow visualOverflowForDecorations(const RenderStyle& style)
return computedVisualOverflowForDecorations(style, underlineOffset);
}

float underlineOffsetForTextBoxPainting(const RenderStyle& style, const InlineIterator::TextBoxIterator& textBox)
float underlineOffsetForTextBoxPainting(const InlineIterator::InlineBox& inlineBox, const RenderStyle& style)
{
auto textUnderlinePositionUnder = std::optional<TextUnderlinePositionUnder> { };
auto underlinePositionValue = resolvedUnderlinePosition(style, textBox->lineBox()->baselineType());
auto underlinePositionValue = resolvedUnderlinePosition(style, inlineBox.lineBox()->baselineType());

if (underlinePositionValue == TextUnderlinePosition::Under) {
auto textRunOffset = textRunOffsetFromBottomMost(textBox->lineBox(), textBox->renderer(), textBox->logicalTop(), textBox->logicalBottom());
textUnderlinePositionUnder = TextUnderlinePositionUnder { textBox->logicalBottom() - textBox->logicalTop(), textRunOffset };
auto textRunOffset = boxOffsetFromBottomMost(inlineBox.lineBox(), inlineBox.renderer(), inlineBox.logicalTop(), inlineBox.logicalBottom());
textUnderlinePositionUnder = TextUnderlinePositionUnder { inlineBox.logicalHeight(), textRunOffset };
}

return computedUnderlineOffset({ style, underlinePositionValue, defaultGap(style), textUnderlinePositionUnder });
@@ -25,17 +25,13 @@

#pragma once

#include "FontCascade.h"
#include "InlineIteratorBox.h"
#include "InlineIteratorInlineBox.h"
#include "InlineIteratorLineBox.h"
#include "RenderStyleConstants.h"

namespace WebCore {

class LegacyInlineTextBox;
class RenderElement;
class RenderStyle;
class TextUnderlineOffset;

inline float wavyOffsetFromDecoration()
{
@@ -65,6 +61,6 @@ GlyphOverflow visualOverflowForDecorations(const RenderStyle&);
GlyphOverflow visualOverflowForDecorations(const RenderStyle&, FontBaseline, TextUnderlinePositionUnder);
GlyphOverflow visualOverflowForDecorations(const InlineIterator::LineBoxIterator&, const RenderText&, float textBoxLogicalTop, float textBoxLogicalBottom);

float underlineOffsetForTextBoxPainting(const RenderStyle&, const InlineIterator::TextBoxIterator&);
float underlineOffsetForTextBoxPainting(const InlineIterator::InlineBox&, const RenderStyle&);

} // namespace WebCore

0 comments on commit 519c36a

Please sign in to comment.