Changeset 252972 in webkit
- Timestamp:
- Dec 1, 2019 3:14:38 PM (4 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 6 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r252971 r252972 1 2019-12-01 Zalan Bujtas <zalan@apple.com> 2 3 [LFC][IFC] Trim trailing letter spacing. 4 https://bugs.webkit.org/show_bug.cgi?id=204731 5 <rdar://problem/57545763> 6 7 Reviewed by Antti Koivisto. 8 9 Refactor trailing trimmable content to support partial, non-whitespace trimmable content e.g. letter spacing. 10 https://drafts.csswg.org/css-text-3/#letter-spacing-property -> UAs therefore must not append letter spacing to the right or trailing edge of a line. 11 12 * layout/inlineformatting/InlineLineBreaker.cpp: 13 (WebCore::Layout::LineBreaker::breakingContextForInlineContent): 14 (WebCore::Layout::LineBreaker::Content::append): 15 (WebCore::Layout::LineBreaker::Content::reset): 16 (WebCore::Layout::LineBreaker::Content::TrailingTrimmableContent::reset): 17 (WebCore::Layout::LineBreaker::Content::hasNonWhitespaceOrInlineBox const): Deleted. 18 (WebCore::Layout::LineBreaker::Content::trailingTrimmableWidth const): Deleted. 19 * layout/inlineformatting/InlineLineBreaker.h: 20 (WebCore::Layout::LineBreaker::Content::isEmpty const): 21 (WebCore::Layout::LineBreaker::Content::nonTrimmableWidth const): 22 (WebCore::Layout::LineBreaker::Content::hasTrailingTrimmableContent const): 23 (WebCore::Layout::LineBreaker::Content::isTrailingContentFullyTrimmable const): 24 * layout/inlineformatting/InlineLineBuilder.cpp: 25 (WebCore::Layout::LineBuilder::removeTrailingTrimmableContent): 26 (WebCore::Layout::LineBuilder::appendTextContent): 27 (WebCore::Layout::LineBuilder::TrimmableContent::append): 28 (WebCore::Layout::LineBuilder::InlineItemRun::removeTrailingLetterSpacing): 29 * layout/inlineformatting/InlineLineBuilder.h: 30 (WebCore::Layout::LineBuilder::isTrailingContentFullyTrimmable const): 31 (WebCore::Layout::LineBuilder::TrimmableContent::isTrailingContentFullyTrimmable const): 32 (WebCore::Layout::LineBuilder::TrimmableContent::clear): 33 * layout/inlineformatting/LineLayoutContext.cpp: 34 (WebCore::Layout::LineLayoutContext::processUncommittedContent): 35 1 36 2019-12-01 Simon Fraser <simon.fraser@apple.com> 2 37 -
trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
r252958 r252972 50 50 } 51 51 52 LineBreaker::BreakingContext LineBreaker::breakingContextForInlineContent(const Content& content, LayoutUnit availableWidth, LayoutUnit trailingTrimmableWidth, bool lineIsEmpty)53 { 54 ASSERT(! content.isEmpty());55 if ( content.width() <= availableWidth)52 LineBreaker::BreakingContext LineBreaker::breakingContextForInlineContent(const Content& newContent, LayoutUnit availableWidth, bool lineHasFullyTrimmableTrailingContent, bool lineIsEmpty) 53 { 54 ASSERT(!newContent.isEmpty()); 55 if (newContent.width() <= availableWidth) 56 56 return { BreakingContext::ContentBreak::Keep, { } }; 57 // Check if it fits without the trailing trimmable content. 58 if (!TextUtil::shouldPreserveTrailingWhitespace(content.runs()[0].inlineItem.style())) { 59 auto adjustedAvailableWidth = availableWidth; 60 auto adjustedContentWidth = content.width() - content.trailingTrimmableWidth(); 61 // When the uncommitted content has trailing trimmable width, it also means that it does not have any other non-trimmable _content_ ([text][ ] <- [text] is on commit boundary) 62 // so placing it on the line with trimmable trailing content means that we'v got adjacent trimmable content. 63 // [text][ ] <- existing content on the line with trailing trimmable 64 // [container start][ ][container end] <- new content 65 // <- adjacent trimmable _content_ [text][][container start][][container end] 66 if (trailingTrimmableWidth && !content.hasNonWhitespaceOrInlineBox()) 67 adjustedAvailableWidth += trailingTrimmableWidth; 68 69 if (adjustedContentWidth <= adjustedAvailableWidth) 57 if (newContent.hasTrailingTrimmableContent()) { 58 // First check if the content fits without the trailing trimmable part. 59 if (newContent.nonTrimmableWidth() <= availableWidth) 70 60 return { BreakingContext::ContentBreak::Keep, { } }; 71 } 72 73 if (content.hasTextContentOnly()) { 74 auto& runs = content.runs(); 61 // Now check if we can trim the line too. 62 if (lineHasFullyTrimmableTrailingContent && newContent.isTrailingContentFullyTrimmable()) { 63 // If this new content is fully trimmable, it shoud surely fit. 64 return { BreakingContext::ContentBreak::Keep, { } }; 65 } 66 } 67 68 if (newContent.hasTextContentOnly()) { 69 auto& runs = newContent.runs(); 75 70 if (auto trailingPartialContent = wordBreakingBehavior(runs, availableWidth)) 76 71 return { BreakingContext::ContentBreak::Split, trailingPartialContent }; … … 261 256 void LineBreaker::Content::append(const InlineItem& inlineItem, LayoutUnit logicalWidth) 262 257 { 258 ASSERT(!inlineItem.isFloat()); 263 259 ASSERT(inlineItem.isForcedLineBreak() || !isAtContentBoundary(inlineItem, *this)); 264 260 m_continousRuns.append({ inlineItem, logicalWidth }); 265 261 m_width += logicalWidth; 262 // Figure out the trailing trimmable state. 263 if (inlineItem.isBox() || inlineItem.isForcedLineBreak()) 264 m_trailingTrimmableContent.reset(); 265 else if (inlineItem.isText()) { 266 auto& inlineTextItem = downcast<InlineTextItem>(inlineItem); 267 auto isFullyTrimmable = [&] { 268 return inlineTextItem.isWhitespace() && !TextUtil::shouldPreserveTrailingWhitespace(inlineTextItem.style()); 269 }; 270 if (isFullyTrimmable()) { 271 m_trailingTrimmableContent.width += logicalWidth; 272 m_trailingTrimmableContent.isFullyTrimmable = true; 273 } else if (auto trimmableWidth = inlineTextItem.style().letterSpacing()) { 274 m_trailingTrimmableContent.width = trimmableWidth; 275 m_trailingTrimmableContent.isFullyTrimmable = false; 276 } else 277 m_trailingTrimmableContent.reset(); 278 } 266 279 } 267 280 … … 269 282 { 270 283 m_continousRuns.clear(); 284 m_trailingTrimmableContent.reset(); 271 285 m_width = 0; 272 286 } … … 277 291 m_width -= m_continousRuns[i].logicalWidth; 278 292 m_continousRuns.shrink(newSize); 279 }280 281 bool LineBreaker::Content::hasNonWhitespaceOrInlineBox() const282 {283 // <span><img></span> is considered a inline box run even with the [container start][container end] inline items.284 for (auto& run : m_continousRuns) {285 auto& inlineItem = run.inlineItem;286 if (inlineItem.isContainerStart() || inlineItem.isContainerEnd() || inlineItem.isForcedLineBreak())287 continue;288 return !is<InlineTextItem>(inlineItem) || !downcast<InlineTextItem>(inlineItem).isWhitespace();289 }290 return false;291 293 } 292 294 … … 304 306 } 305 307 306 LayoutUnit LineBreaker::Content::trailingTrimmableWidth() const 307 { 308 LayoutUnit trailingWhitespaceWidth; 309 for (auto i = m_continousRuns.size(); i--;) { 310 auto& inlineItem = m_continousRuns[i].inlineItem; 311 if (inlineItem.isContainerStart() || inlineItem.isContainerEnd()) 312 continue; 313 if (inlineItem.isText() && downcast<InlineTextItem>(inlineItem).isWhitespace()) { 314 trailingWhitespaceWidth += m_continousRuns[i].logicalWidth; 315 continue; 316 } 317 // Can't have [text content][whitespace] as [whitespace] is always a commit boundary when it is adjacent to some other inline content. See UncommittedContent::add. 318 ASSERT(!trailingWhitespaceWidth); 319 return trailingWhitespaceWidth; 320 } 321 return trailingWhitespaceWidth; 322 } 308 void LineBreaker::Content::TrailingTrimmableContent::reset() 309 { 310 isFullyTrimmable = false; 311 width = { }; 312 } 313 323 314 324 315 } -
trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h
r252958 r252972 75 75 const RunList& runs() const { return m_continousRuns; } 76 76 bool isEmpty() const { return m_continousRuns.isEmpty(); } 77 bool hasNonWhitespaceOrInlineBox() const;78 77 bool hasTextContentOnly() const; 79 78 unsigned size() const { return m_continousRuns.size(); } 80 79 LayoutUnit width() const { return m_width; } 81 LayoutUnit trailingTrimmableWidth() const; 80 LayoutUnit nonTrimmableWidth() const { return m_width - m_trailingTrimmableContent.width; } 81 82 bool hasTrailingTrimmableContent() const { return !!m_trailingTrimmableContent.width; } 83 bool isTrailingContentFullyTrimmable() const { return m_trailingTrimmableContent.isFullyTrimmable; } 82 84 83 85 private: 84 86 RunList m_continousRuns; 87 struct TrailingTrimmableContent { 88 void reset(); 89 90 bool isFullyTrimmable { false }; 91 LayoutUnit width; 92 }; 93 TrailingTrimmableContent m_trailingTrimmableContent; 85 94 LayoutUnit m_width; 86 95 }; 87 96 88 BreakingContext breakingContextForInlineContent(const Content&, LayoutUnit availableWidth, LayoutUnit trailingTrimmableWidth, bool lineIsEmpty);97 BreakingContext breakingContextForInlineContent(const Content&, LayoutUnit availableWidth, bool lineHasFullyTrimmableTrailingContent, bool lineIsEmpty); 89 98 bool shouldWrapFloatBox(LayoutUnit floatLogicalWidth, LayoutUnit availableWidth, bool lineIsEmpty); 90 99 -
trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.cpp
r252967 r252972 384 384 auto& trimmableRun = m_inlineItemRuns[trimmableRunIndex]; 385 385 ASSERT(trimmableRun.isText()); 386 // FIXME: We might need to be able to differentiate between trimmed and collapsed runs. 387 trimmableRun.setCollapsesToZeroAdvanceWidth(); 386 if (trimmableRun.isWhitespace()) 387 trimmableRun.setCollapsesToZeroAdvanceWidth(); 388 else 389 trimmableRun.removeTrailingLetterSpacing(); 388 390 } 389 391 m_lineBox.shrinkHorizontally(m_trimmableContent.width()); 390 392 m_trimmableContent.clear(); 391 393 // If we trimmed the first visible run on the line, we need to re-check the visibility status. 392 ASSERT(m_lineIsVisuallyEmptyBeforeTrimmableContent.hasValue()); 393 if (*m_lineIsVisuallyEmptyBeforeTrimmableContent) { 394 if (m_lineIsVisuallyEmptyBeforeTrimmableContent) { 394 395 // Just because the line was visually empty before the trimmed content, it does not necessarily mean it is still visually empty. 395 396 // <span> </span><span style="padding-left: 10px"></span> <- non-empty … … 404 405 if (lineIsVisuallyEmpty()) 405 406 m_lineBox.setIsConsideredEmpty(); 406 m_lineIsVisuallyEmptyBeforeTrimmableContent = { };407 }407 } 408 m_lineIsVisuallyEmptyBeforeTrimmableContent = { }; 408 409 } 409 410 … … 466 467 void LineBuilder::appendTextContent(const InlineTextItem& inlineItem, LayoutUnit logicalWidth) 467 468 { 468 auto isTrimmable = !shouldPreserveTrailingContent(inlineItem);469 if (!isTrimmable)470 m_trimmableContent.clear();471 472 469 auto willCollapseCompletely = [&] { 473 470 // Empty run. … … 513 510 514 511 auto lineRunWidth = lineRun.logicalRect().width(); 515 if (isTrimmable) { 512 // Trailing whitespace content is fully trimmable so as the trailing letter space. 513 auto isFullyTrimmable = !shouldPreserveTrailingContent(inlineItem); 514 auto isPartiallyTrimmable = !inlineItem.isWhitespace() && inlineItem.style().letterSpacing(); 515 auto isTrimmable = isFullyTrimmable || isPartiallyTrimmable; 516 // Reset the trimmable content if needed. 517 if (!isTrimmable || isPartiallyTrimmable || (isFullyTrimmable && !m_trimmableContent.isTrailingContentFullyTrimmable())) 518 m_trimmableContent.clear(); 519 if (isFullyTrimmable) { 516 520 // If we ever trim this content, we need to know if the line visibility state needs to be recomputed. 517 521 if (m_trimmableContent.isEmpty()) 518 522 m_lineIsVisuallyEmptyBeforeTrimmableContent = isVisuallyEmpty(); 519 m_trimmableContent.append(lineRunWidth, m_inlineItemRuns.size()); 520 } 523 m_trimmableContent.append(lineRunWidth, m_inlineItemRuns.size(), TrimmableContent::IsFullyTrimmable::Yes); 524 } else if (isPartiallyTrimmable) 525 m_trimmableContent.append(LayoutUnit { inlineItem.style().letterSpacing() }, m_inlineItemRuns.size(), TrimmableContent::IsFullyTrimmable::No); 521 526 522 527 m_lineBox.expandHorizontally(lineRunWidth); … … 709 714 } 710 715 711 void LineBuilder::TrimmableContent::append(LayoutUnit itemRunWidth, size_t runIndex )716 void LineBuilder::TrimmableContent::append(LayoutUnit itemRunWidth, size_t runIndex, IsFullyTrimmable isFullyTrimmable) 712 717 { 713 718 // word-spacing could very well be negative, but it does not mean that the line gains that much extra space when the content is trimmed. 714 719 itemRunWidth = std::max(0_lu, itemRunWidth); 715 720 m_width += itemRunWidth; 721 m_lastRunIsFullyTrimmable = isFullyTrimmable == IsFullyTrimmable::Yes; 716 722 m_runIndexes.append(runIndex); 717 723 } … … 730 736 } 731 737 738 void LineBuilder::InlineItemRun::removeTrailingLetterSpacing() 739 { 740 ASSERT(m_inlineItem.style().letterSpacing()); 741 m_logicalRect.expandHorizontally(LayoutUnit { -m_inlineItem.style().letterSpacing() }); 742 } 743 732 744 } 733 745 } -
trunk/Source/WebCore/layout/inlineformatting/InlineLineBuilder.h
r252928 r252972 66 66 67 67 LayoutUnit trailingTrimmableWidth() const { return m_trimmableContent.width(); } 68 bool isTrailingContentFullyTrimmable() const { return m_trimmableContent.isTrailingContentFullyTrimmable(); } 68 69 69 70 const LineBox& lineBox() const { return m_lineBox; } … … 171 172 bool isCollapsed() const { return m_isCollapsed; } 172 173 174 void removeTrailingLetterSpacing(); 173 175 void setCollapsesToZeroAdvanceWidth(); 174 176 bool isCollapsedToZeroAdvanceWidth() const { return m_collapsedToZeroAdvanceWidth; } … … 188 190 189 191 struct TrimmableContent { 190 void append(LayoutUnit itemRunWidth, size_t runIndex); 192 enum class IsFullyTrimmable { No, Yes }; 193 void append(LayoutUnit itemRunWidth, size_t runIndex, IsFullyTrimmable); 191 194 void clear(); 192 195 … … 194 197 Vector<size_t, 5>& runIndexes() { return m_runIndexes; } 195 198 bool isEmpty() const { return m_runIndexes.isEmpty(); } 199 bool isTrailingContentFullyTrimmable() const { return m_lastRunIsFullyTrimmable; } 196 200 197 201 private: 198 202 Vector<size_t, 5> m_runIndexes; 199 203 LayoutUnit m_width; 204 bool m_lastRunIsFullyTrimmable { false }; 200 205 }; 201 206 … … 216 221 m_runIndexes.clear(); 217 222 m_width = { }; 223 m_lastRunIsFullyTrimmable = false; 218 224 } 219 225 -
trunk/Source/WebCore/layout/inlineformatting/LineLayoutContext.cpp
r252967 r252972 210 210 if (shouldDisableHyphenation()) 211 211 lineBreaker.setHyphenationDisabled(); 212 auto breakingContext = lineBreaker.breakingContextForInlineContent(m_uncommittedContent, line.availableWidth(), line. trailingTrimmableWidth(), lineIsConsideredEmpty);212 auto breakingContext = lineBreaker.breakingContextForInlineContent(m_uncommittedContent, line.availableWidth(), line.isTrailingContentFullyTrimmable(), lineIsConsideredEmpty); 213 213 // The uncommitted content can fully, partially fit the current line (commit/partial commit) or not at all (reset). 214 214 if (breakingContext.contentBreak == LineBreaker::BreakingContext::ContentBreak::Keep)
Note: See TracChangeset
for help on using the changeset viewer.