| Differences between
and this patch
- a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp -35 / +75 lines
Lines 153-163 InlineDisplay::Line InlineDisplayLineBuilder::build(const LineBuilder::LineConte a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp_sec1
153
    };
153
    };
154
}
154
}
155
155
156
static float truncateTextContentWithMismatchingDirection(InlineDisplay::Box& displayBox, float contentWidth, float availableWidthForContent, bool canFullyTruncate)
157
{
158
    // While truncation normally starts at the end of the content and progress backwards, with mismatching direction
159
    // we take a different approach and truncate content the other way around (i.e. ellipsis follows inline direction truncating the beginning of the content).
160
    // <div dir=rtl>some long content</div>
161
    // [...ng content]
162
    auto& inlineTextBox = downcast<InlineTextBox>(displayBox.layoutBox());
163
    auto& textContent = displayBox.text();
164
165
    auto availableWidthForTruncatedContent = contentWidth - availableWidthForContent;
166
    auto truncatedSide = TextUtil::breakWord(inlineTextBox, textContent.start(), textContent.length(), contentWidth, availableWidthForTruncatedContent, { }, displayBox.style().fontCascade());
167
168
    ASSERT(truncatedSide.length < textContent.length());
169
    auto visibleLength = textContent.length() - truncatedSide.length;
170
    auto visibleWidth = contentWidth - truncatedSide.logicalWidth;
171
    // TextUtil::breakWord never returns overflowing content (left side) which means the right side is normally wider (by one character) than what we need.
172
    auto visibleContentOverflows = truncatedSide.logicalWidth < availableWidthForTruncatedContent;
173
    if (!visibleContentOverflows) {
174
        textContent.setPartiallyVisibleContentLength(visibleLength);
175
        return visibleWidth;
176
    }
177
178
    auto wouldFullyTruncate = visibleLength <= 1;
179
    if (wouldFullyTruncate && canFullyTruncate) {
180
        displayBox.setIsFullyTruncated();
181
        return { };
182
    }
183
184
    visibleLength = !wouldFullyTruncate ? visibleLength - 1 : 1;
185
    textContent.setPartiallyVisibleContentLength(visibleLength);
186
    auto visibleStartPosition = textContent.end() - visibleLength;
187
    return TextUtil::width(inlineTextBox, displayBox.style().fontCascade(), visibleStartPosition, textContent.end(), { }, TextUtil::UseTrailingWhitespaceMeasuringOptimization::No);
188
}
189
190
static float truncate(InlineDisplay::Box& displayBox, float contentWidth, float availableWidthForContent, bool canFullyTruncate)
191
{
192
    if (displayBox.isText()) {
193
        if (!availableWidthForContent && canFullyTruncate) {
194
            displayBox.setIsFullyTruncated();
195
            return { };
196
        }
197
        auto contentDirection = displayBox.bidiLevel() % 2 ? TextDirection::RTL : TextDirection::LTR;
198
        if (displayBox.layoutBox().parent().style().direction() != contentDirection)
199
            return truncateTextContentWithMismatchingDirection(displayBox, contentWidth, availableWidthForContent, canFullyTruncate);
200
201
        auto& inlineTextBox = downcast<InlineTextBox>(displayBox.layoutBox());
202
        auto& textContent = displayBox.text();
203
        auto visibleSide = TextUtil::breakWord(inlineTextBox, textContent.start(), textContent.length(), contentWidth, availableWidthForContent, { }, displayBox.style().fontCascade());
204
        if (visibleSide.length) {
205
            textContent.setPartiallyVisibleContentLength(visibleSide.length);
206
            return visibleSide.logicalWidth;
207
        }
208
        if (canFullyTruncate) {
209
            displayBox.setIsFullyTruncated();
210
            return { };
211
        }
212
        auto firstCharacterLength = TextUtil::firstUserPerceivedCharacterLength(inlineTextBox, textContent.start(), textContent.length());
213
        auto firstCharacterWidth = TextUtil::width(inlineTextBox, displayBox.style().fontCascade(), textContent.start(), textContent.start() + firstCharacterLength, { }, TextUtil::UseTrailingWhitespaceMeasuringOptimization::No);
214
        textContent.setPartiallyVisibleContentLength(firstCharacterLength);
215
        return firstCharacterWidth;
216
    }
217
218
    if (canFullyTruncate) {
219
        // Atomic inline level boxes are never partially truncated.
220
        displayBox.setIsFullyTruncated();
221
        return { };
222
    }
223
    return contentWidth;
224
}
225
156
static float truncateOverflowingDisplayBoxes(const InlineDisplay::Line& displayLine, InlineDisplay::Boxes& boxes, float ellipsisWidth, const RenderStyle& rootStyle)
226
static float truncateOverflowingDisplayBoxes(const InlineDisplay::Line& displayLine, InlineDisplay::Boxes& boxes, float ellipsisWidth, const RenderStyle& rootStyle)
157
{
227
{
158
    // We gotta truncate some runs.
228
    // We gotta truncate some runs.
159
    ASSERT(displayLine.lineBoxLogicalRect().x() + displayLine.contentLogicalLeft() + displayLine.contentLogicalWidth() + ellipsisWidth > displayLine.lineBoxLogicalRect().maxX());
229
    ASSERT(displayLine.lineBoxLogicalRect().x() + displayLine.contentLogicalLeft() + displayLine.contentLogicalWidth() + ellipsisWidth > displayLine.lineBoxLogicalRect().maxX());
160
    auto isLeftToRightDirection = rootStyle.isLeftToRightDirection();
161
    auto isHorizontal = rootStyle.isHorizontalWritingMode();
230
    auto isHorizontal = rootStyle.isHorizontalWritingMode();
162
    auto left = [&] (auto& displayBox) {
231
    auto left = [&] (auto& displayBox) {
163
        return isHorizontal ? displayBox.left() : displayBox.top();
232
        return isHorizontal ? displayBox.left() : displayBox.top();
Lines 168-214 static float truncateOverflowingDisplayBoxes(const InlineDisplay::Line& displayL a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp_sec2
168
    auto width = [&] (auto& displayBox) {
237
    auto width = [&] (auto& displayBox) {
169
        return isHorizontal ? displayBox.width() : displayBox.height();
238
        return isHorizontal ? displayBox.width() : displayBox.height();
170
    };
239
    };
171
    auto truncate = [&] (auto& displayBox, auto visibleWidth, auto canFullyTruncate) {
172
        if (displayBox.isText()) {
173
            if (!visibleWidth && canFullyTruncate) {
174
                displayBox.setIsFullyTruncated();
175
                return isLeftToRightDirection ? left(displayBox) : right(displayBox);
176
            }
177
178
            auto& inlineTextBox = downcast<InlineTextBox>(displayBox.layoutBox());
179
            auto& textContent = displayBox.text();
180
            auto leftSide = TextUtil::breakWord(inlineTextBox, textContent.start(), textContent.length(), width(displayBox), visibleWidth, { }, displayBox.style().fontCascade());
181
            if (leftSide.length) {
182
                textContent.setPartiallyVisibleContentLength(leftSide.length);
183
                return isLeftToRightDirection ? left(displayBox) + leftSide.logicalWidth : right(displayBox) - leftSide.logicalWidth;
184
            }
185
            if (canFullyTruncate) {
186
                displayBox.setIsFullyTruncated();
187
                return isLeftToRightDirection ? left(displayBox) : right(displayBox);
188
            }
189
            auto firstCharacterLength = TextUtil::firstUserPerceivedCharacterLength(inlineTextBox, textContent.start(), textContent.length());
190
            auto firstCharacterWidth = TextUtil::width(inlineTextBox, displayBox.style().fontCascade(), textContent.start(), textContent.start() + firstCharacterLength, { }, TextUtil::UseTrailingWhitespaceMeasuringOptimization::No);
191
            textContent.setPartiallyVisibleContentLength(firstCharacterLength);
192
            return isLeftToRightDirection ? left(displayBox) + firstCharacterWidth : right(displayBox) - firstCharacterWidth;
193
        }
194
        if (canFullyTruncate) {
195
            // Atomic inline level boxes are never partially truncated.
196
            displayBox.setIsFullyTruncated();
197
            return isLeftToRightDirection ? left(displayBox) : right(displayBox);
198
        }
199
        return isLeftToRightDirection ? right(displayBox) : left(displayBox);
200
    };
201
202
    // The logically first character or atomic inline-level element on a line must be clipped rather than ellipsed.
240
    // The logically first character or atomic inline-level element on a line must be clipped rather than ellipsed.
203
    auto isFirstContentRun = true;
241
    auto isFirstContentRun = true;
204
    if (isLeftToRightDirection) {
242
    if (rootStyle.isLeftToRightDirection()) {
205
        auto visualRightForContentEnd = std::max(0.f, right(displayLine) - ellipsisWidth);
243
        auto visualRightForContentEnd = std::max(0.f, right(displayLine) - ellipsisWidth);
206
        auto truncateRight = std::optional<float> { };
244
        auto truncateRight = std::optional<float> { };
207
        for (auto& displayBox : boxes) {
245
        for (auto& displayBox : boxes) {
208
            if (displayBox.isInlineBox())
246
            if (displayBox.isInlineBox())
209
                continue;
247
                continue;
210
            if (right(displayBox) > visualRightForContentEnd) {
248
            if (right(displayBox) > visualRightForContentEnd) {
211
                auto truncatePosition = truncate(displayBox, std::max(0.f, visualRightForContentEnd - left(displayBox)), !isFirstContentRun);
249
                auto visibleWidth = truncate(displayBox, width(displayBox), std::max(0.f, visualRightForContentEnd - left(displayBox)), !isFirstContentRun);
250
                auto truncatePosition = left(displayBox) + visibleWidth;
212
                truncateRight = truncateRight.value_or(truncatePosition);
251
                truncateRight = truncateRight.value_or(truncatePosition);
213
            }
252
            }
214
            isFirstContentRun = false;
253
            isFirstContentRun = false;
Lines 223-229 static float truncateOverflowingDisplayBoxes(const InlineDisplay::Line& displayL a/Source/WebCore/layout/formattingContexts/inline/display/InlineDisplayLineBuilder.cpp_sec3
223
        if (displayBox.isInlineBox())
262
        if (displayBox.isInlineBox())
224
            continue;
263
            continue;
225
        if (left(displayBox) < visualLeftForContentEnd) {
264
        if (left(displayBox) < visualLeftForContentEnd) {
226
            auto truncatePosition = truncate(displayBox, std::max(0.f, right(displayBox) - visualLeftForContentEnd), !isFirstContentRun);
265
            auto visibleWidth = truncate(displayBox, width(displayBox), std::max(0.f, right(displayBox) - visualLeftForContentEnd), !isFirstContentRun);
266
            auto truncatePosition = right(displayBox) - visibleWidth;
227
            truncateLeft = truncateLeft.value_or(truncatePosition);
267
            truncateLeft = truncateLeft.value_or(truncatePosition);
228
        }
268
        }
229
        isFirstContentRun = false;
269
        isFirstContentRun = false;
- a/Source/WebCore/rendering/TextBoxPainter.cpp -20 / +10 lines
Lines 245-250 void TextBoxPainter<TextBoxPath>::paintForegroundAndDecorations() a/Source/WebCore/rendering/TextBoxPainter.cpp_sec1
245
    auto hasTextDecoration = !m_style.textDecorationsInEffect().isEmpty();
245
    auto hasTextDecoration = !m_style.textDecorationsInEffect().isEmpty();
246
    auto hasHighlightDecoration = m_document.hasHighlight() && !MarkedText::collectForHighlights(m_renderer, m_selectableRange, MarkedText::PaintPhase::Decoration).isEmpty();
246
    auto hasHighlightDecoration = m_document.hasHighlight() && !MarkedText::collectForHighlights(m_renderer, m_selectableRange, MarkedText::PaintPhase::Decoration).isEmpty();
247
    auto hasDecoration = hasTextDecoration || hasHighlightDecoration;
247
    auto hasDecoration = hasTextDecoration || hasHighlightDecoration;
248
    auto hasMismatchingContentDirection = m_renderer.containingBlock()->style().direction() != textBox().direction();
249
    auto hasBackwardTrunctation = m_selectableRange.truncation && hasMismatchingContentDirection;
248
250
249
    auto contentMayNeedStyledMarkedText = [&] {
251
    auto contentMayNeedStyledMarkedText = [&] {
250
        if (hasDecoration)
252
        if (hasDecoration)
Lines 257-265 void TextBoxPainter<TextBoxPath>::paintForegroundAndDecorations() a/Source/WebCore/rendering/TextBoxPainter.cpp_sec2
257
            return true;
259
            return true;
258
        return false;
260
        return false;
259
    };
261
    };
262
    auto startPosition = [&] {
263
        return !hasBackwardTrunctation ? m_selectableRange.clamp(textBox().start()) : textBox().length() - *m_selectableRange.truncation;
264
    };
265
    auto endPosition = [&] {
266
        return !hasBackwardTrunctation ? m_selectableRange.clamp(textBox().end()) : textBox().length();
267
    };
260
    if (!contentMayNeedStyledMarkedText()) {
268
    if (!contentMayNeedStyledMarkedText()) {
261
        auto& lineStyle = m_isFirstLine ? m_renderer.firstLineStyle() : m_renderer.style();
269
        auto& lineStyle = m_isFirstLine ? m_renderer.firstLineStyle() : m_renderer.style();
262
        auto markedText = MarkedText { m_selectableRange.clamp(textBox().start()), m_selectableRange.clamp(textBox().end()), MarkedText::Unmarked };
270
        auto markedText = MarkedText { startPosition(), endPosition(), MarkedText::Unmarked };
263
        auto styledMarkedText = StyledMarkedText { markedText, StyledMarkedText::computeStyleForUnmarkedMarkedText(m_renderer, lineStyle, m_isFirstLine, m_paintInfo) };
271
        auto styledMarkedText = StyledMarkedText { markedText, StyledMarkedText::computeStyleForUnmarkedMarkedText(m_renderer, lineStyle, m_isFirstLine, m_paintInfo) };
264
        paintCompositionForeground(styledMarkedText);
272
        paintCompositionForeground(styledMarkedText);
265
        return;
273
        return;
Lines 268-274 void TextBoxPainter<TextBoxPath>::paintForegroundAndDecorations() a/Source/WebCore/rendering/TextBoxPainter.cpp_sec3
268
    Vector<MarkedText> markedTexts;
276
    Vector<MarkedText> markedTexts;
269
    if (m_paintInfo.phase != PaintPhase::Selection) {
277
    if (m_paintInfo.phase != PaintPhase::Selection) {
270
        // The marked texts for the gaps between document markers and selection are implicitly created by subdividing the entire line.
278
        // The marked texts for the gaps between document markers and selection are implicitly created by subdividing the entire line.
271
        markedTexts.append({ m_selectableRange.clamp(textBox().start()), m_selectableRange.clamp(textBox().end()), MarkedText::Unmarked });
279
        markedTexts.append({ startPosition(), endPosition(), MarkedText::Unmarked });
272
280
273
        if (!m_isPrinting) {
281
        if (!m_isPrinting) {
274
            markedTexts.appendVector(MarkedText::collectForDocumentMarkers(m_renderer, m_selectableRange, MarkedText::PaintPhase::Foreground));
282
            markedTexts.appendVector(MarkedText::collectForDocumentMarkers(m_renderer, m_selectableRange, MarkedText::PaintPhase::Foreground));
Lines 843-867 FloatRect TextBoxPainter<TextBoxPath>::computePaintRect(const LayoutPoint& paint a/Source/WebCore/rendering/TextBoxPainter.cpp_sec4
843
{
851
{
844
    FloatPoint localPaintOffset(paintOffset);
852
    FloatPoint localPaintOffset(paintOffset);
845
853
846
    if (m_selectableRange.truncation) {
847
        if (m_renderer.containingBlock()->style().direction() != textBox().direction()) {
848
            // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin
849
            // at which we start drawing text.
850
            // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is:
851
            // |Hello|CBA| -> |...He|CBA|
852
            // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing
853
            // farther to the right.
854
            // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the
855
            // truncated string i.e.  |Hello|CBA| -> |...lo|CBA|
856
            LayoutUnit widthOfVisibleText { m_renderer.width(textBox().start(), *m_selectableRange.truncation, textPosition(), m_isFirstLine) };
857
            LayoutUnit widthOfHiddenText { m_logicalRect.width() - widthOfVisibleText };
858
            LayoutSize truncationOffset(textBox().direction() == TextDirection::LTR ? widthOfHiddenText : -widthOfHiddenText, 0_lu);
859
            localPaintOffset.move(textBox().isHorizontal() ? truncationOffset : truncationOffset.transposedSize());
860
        }
861
    }
862
863
    localPaintOffset.move(0, m_style.isHorizontalWritingMode() ? 0 : -m_logicalRect.height());
854
    localPaintOffset.move(0, m_style.isHorizontalWritingMode() ? 0 : -m_logicalRect.height());
864
865
    auto visualRect = textBox().visualRectIgnoringBlockDirection();
855
    auto visualRect = textBox().visualRectIgnoringBlockDirection();
866
    textBox().formattingContextRoot().flipForWritingMode(visualRect);
856
    textBox().formattingContextRoot().flipForWritingMode(visualRect);
867
857
- a/Source/WebCore/rendering/TextBoxSelectableRange.h +1 lines
Lines 33-38 struct TextBoxSelectableRange { a/Source/WebCore/rendering/TextBoxSelectableRange.h_sec1
33
    const unsigned length;
33
    const unsigned length;
34
    const unsigned additionalLengthAtEnd { 0 };
34
    const unsigned additionalLengthAtEnd { 0 };
35
    const bool isLineBreak { false };
35
    const bool isLineBreak { false };
36
    // FIXME: Consider holding onto the truncation position instead. See webkit.org/b/164999
36
    const std::optional<unsigned> truncation { };
37
    const std::optional<unsigned> truncation { };
37
38
38
    unsigned clamp(unsigned offset) const
39
    unsigned clamp(unsigned offset) const
- a/LayoutTests/TestExpectations -1 lines
Lines 3305-3311 imported/w3c/web-platform-tests/css/css-display/run-in/run-in-text-between-004.x a/LayoutTests/TestExpectations_sec1
3305
imported/w3c/web-platform-tests/css/css-display/run-in/run-in-text-between-005.xht [ ImageOnlyFailure ]
3305
imported/w3c/web-platform-tests/css/css-display/run-in/run-in-text-between-005.xht [ ImageOnlyFailure ]
3306
imported/w3c/web-platform-tests/css/css-ui/text-overflow-027.html [ ImageOnlyFailure ]
3306
imported/w3c/web-platform-tests/css/css-ui/text-overflow-027.html [ ImageOnlyFailure ]
3307
imported/w3c/web-platform-tests/css/css-ui/text-overflow-028.html [ ImageOnlyFailure ]
3307
imported/w3c/web-platform-tests/css/css-ui/text-overflow-028.html [ ImageOnlyFailure ]
3308
imported/w3c/web-platform-tests/css/css-ui/text-overflow-029.html [ ImageOnlyFailure ]
3309
3308
3310
imported/w3c/web-platform-tests/css/mediaqueries/viewport-script-dynamic.html [ ImageOnlyFailure ]
3309
imported/w3c/web-platform-tests/css/mediaqueries/viewport-script-dynamic.html [ ImageOnlyFailure ]
3311
3310
- a/LayoutTests/fast/inline/text_overflow_ellipsis_simple-expected.html -4 / +4 lines
Lines 10-16 div { a/LayoutTests/fast/inline/text_overflow_ellipsis_simple-expected.html_sec1
10
<div style="border-left: 10px solid">AB&#8230;</div>
10
<div style="border-left: 10px solid">AB&#8230;</div>
11
<div style="border-left: 10px solid; padding-left: 20px;">AB&#8230;</div>
11
<div style="border-left: 10px solid; padding-left: 20px;">AB&#8230;</div>
12
<div style="border: 10px solid; padding: 20px;">AB&#8230;</div>
12
<div style="border: 10px solid; padding: 20px;">AB&#8230;</div>
13
<div style="direction: rtl;">EF&#8230;</div>
13
<div style="direction: rtl;">GH&#8230;</div>
14
<div style="direction: rtl; border-left: 10px solid">EF&#8230;</div>
14
<div style="direction: rtl; border-left: 10px solid">GH&#8230;</div>
15
<div style="direction: rtl; border-left: 10px solid; padding-left: 20px;">EF&#8230;</div>
15
<div style="direction: rtl; border-left: 10px solid; padding-left: 20px;">GH&#8230;</div>
16
<div style="direction: rtl; border: 10px solid; padding: 20px;">EF&#8230;</div>
16
<div style="direction: rtl; border: 10px solid; padding: 20px;">GH&#8230;</div>

Return to Bug 164999