Changeset 251455 in webkit
- Timestamp:
- Oct 22, 2019 2:11:48 PM (5 years ago)
- Location:
- trunk/Source/WebCore
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r251454 r251455 1 2019-10-22 Zalan Bujtas <zalan@apple.com> 2 3 [LFC][IFC] Add support for continuous content/commit boundary check 4 https://bugs.webkit.org/show_bug.cgi?id=203255 5 <rdar://problem/56503598> 6 7 Reviewed by Antti Koivisto. 8 9 This patch adds support for continuous content and commit boundary check. 10 11 <span style="padding-right: 10px;">textcontent</span> 12 13 The content above forms a continuous, unbreakable run of ([container start][textcontent][container end with horizontal padding of 10px]). 14 However at this point we don't know yet whether we are at the end of the run and we need to keep checking for the trailing content. 15 In general, we can't submit the run to the line breaking unless we managed to find the commit boundary for the current run. 16 17 <span style="padding-right: 10px;">textcontent</span><img src="broken.jpg"> 18 This content produces two separate runs as follows: 19 1. ([container start][textcontent][container end with horizontal padding of 10px]) 20 2. ([img]) 21 vs. 22 <span style="padding-right: 10px;">textcontent</span>moretextcontent 23 This content produces only one run 24 1. ([container start][textcontent][container end with horizontal padding of 10px][moretextcontent]) 25 26 The idea here is that we don't commit the content on the line unless we identified the run boundary. In practice it means that we hardly commit the current inline item on the line 27 but instead we use it to decide whether the uncommitted content is ready to be committed. 28 29 Using the following example: 30 <span style="padding-right: 10px;">textcontent<img src="broken.jpg"></span> 31 32 Incoming inline items are: 33 [container start] -> we can't identify the run boundary -> add inline item to pending content 34 [textcontent] -> we still can't identify the run boundary sine we don't know what the next inline item is -> add inline item to pending content 35 [img] -> now we know that the [container start][textcontent] is on a commit boundary -> commit pending content -> however the current [img] item's boundary is unknown. 36 [container end with horizontal padding of 10px] -> Now the [img] and [container end] form an unbreakable run but we don't yet know if this is a run boundary 37 - End of content -> always a commit boundary -> commit pending items -> ([img][container end]) 38 39 * layout/inlineformatting/InlineLineBreaker.cpp: 40 (WebCore::Layout::LineBreaker::breakingContext): 41 (WebCore::Layout::LineBreaker::breakingContextForFloat): 42 (WebCore::Layout::LineBreaker::wordBreakingBehavior const): 43 (WebCore::Layout::LineBreaker::isAtBreakingOpportunity): Deleted. 44 * layout/inlineformatting/InlineLineBreaker.h: 45 * layout/inlineformatting/InlineLineLayout.cpp: 46 (WebCore::Layout::LineLayout::layout): 47 (WebCore::Layout::LineLayout::close): 48 (WebCore::Layout::LineLayout::placeInlineItem): 49 (WebCore::Layout::LineLayout::processUncommittedContent): 50 (WebCore::Layout::LineLayout::shouldProcessUncommittedContent const): 51 * layout/inlineformatting/InlineLineLayout.h: 52 (WebCore::Layout::LineLayout::UncommittedContent::runs): 53 (WebCore::Layout::LineLayout::UncommittedContent::runs const): 54 1 55 2019-10-22 Dean Jackson <dino@apple.com> 2 56 -
trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.cpp
r250766 r251455 36 36 namespace Layout { 37 37 38 LineBreaker::Breaking Context LineBreaker::breakingContext(const InlineItem& inlineItem, LayoutUnit logicalWidth, const LineContext& lineContext)38 LineBreaker::BreakingBehavior LineBreaker::breakingContext(const Vector<LineLayout::Run>& runs, LayoutUnit logicalWidth, LayoutUnit availableWidth, bool lineIsEmpty) 39 39 { 40 40 // First content always stays on line. 41 if (line Context.isEmpty || logicalWidth <= lineContext.availableWidth)42 return { BreakingBehavior::Keep, isAtBreakingOpportunity(inlineItem) };41 if (lineIsEmpty || logicalWidth <= availableWidth) 42 return BreakingBehavior::Keep; 43 43 44 if (inlineItem.isHardLineBreak()) 45 return { BreakingBehavior::Keep, isAtBreakingOpportunity(inlineItem) }; 44 // FIXME: Look inside the list to find out whether it requires text content related line breaking (e.g <span>text</span> <- the first inline item here is a 'container start' but the content is text only). 45 auto& inlineItem = runs.first().inlineItem; 46 if (is<InlineTextItem>(inlineItem)) 47 return wordBreakingBehavior(runs, lineIsEmpty); 46 48 47 if (is<InlineTextItem>(inlineItem)) 48 return { wordBreakingBehavior(downcast<InlineTextItem>(inlineItem), lineContext.isEmpty), isAtBreakingOpportunity(inlineItem) }; 49 50 // Wrap non-text boxes to the next line unless we can trim trailing whitespace. 51 auto availableWidth = lineContext.availableWidth + lineContext.trimmableWidth; 52 if (logicalWidth <= availableWidth) 53 return { BreakingBehavior::Keep, isAtBreakingOpportunity(inlineItem) }; 54 return { BreakingBehavior::Wrap, isAtBreakingOpportunity(inlineItem) }; 49 return BreakingBehavior::Wrap; 55 50 } 56 51 57 LineBreaker::BreakingBehavior LineBreaker:: wordBreakingBehavior(const InlineTextItem& inlineItem, bool lineIsEmpty) const52 LineBreaker::BreakingBehavior LineBreaker::breakingContextForFloat(LayoutUnit floatLogicalWidth, LayoutUnit availableWidth, bool lineIsEmpty) 58 53 { 54 return (lineIsEmpty || floatLogicalWidth <= availableWidth) ? BreakingBehavior::Keep : BreakingBehavior::Wrap; 55 } 56 57 LineBreaker::BreakingBehavior LineBreaker::wordBreakingBehavior(const Vector<LineLayout::Run>& runs, bool lineIsEmpty) const 58 { 59 // FIXME: Check where the overflow occurs and use the corresponding style to figure out the breaking behaviour. 60 // <span style="word-break: normal">first</span><span style="word-break: break-all">second</span><span style="word-break: normal">third</span> 61 auto& inlineItem = downcast<InlineTextItem>(runs.first().inlineItem); 62 59 63 // Word breaking behaviour: 60 64 // 1. Whitesapce collapse on -> push whitespace to next line. … … 87 91 } 88 92 89 bool LineBreaker::isAtBreakingOpportunity(const InlineItem& inlineItem)90 {91 if (inlineItem.isHardLineBreak())92 return true;93 94 if (is<InlineTextItem>(inlineItem))95 return downcast<InlineTextItem>(inlineItem).isWhitespace();96 return !inlineItem.isFloat() && !inlineItem.isContainerStart() && !inlineItem.isContainerEnd();97 }98 99 93 } 100 94 } -
trunk/Source/WebCore/layout/inlineformatting/InlineLineBreaker.h
r246540 r251455 28 28 #if ENABLE(LAYOUT_FORMATTING_CONTEXT) 29 29 30 #include "InlineLineLayout.h" 30 31 #include "LayoutUnit.h" 31 32 … … 39 40 public: 40 41 enum class BreakingBehavior { Keep, Split, Wrap }; 41 struct BreakingContext { 42 BreakingBehavior breakingBehavior; 43 bool isAtBreakingOpportunity { false }; 44 }; 45 struct LineContext { 46 LayoutUnit availableWidth; 47 LayoutUnit logicalLeft; 48 LayoutUnit trimmableWidth; 49 bool isEmpty { false }; 50 }; 51 BreakingContext breakingContext(const InlineItem&, LayoutUnit logicalWidth, const LineContext&); 42 BreakingBehavior breakingContext(const Vector<LineLayout::Run>&, LayoutUnit logicalWidth, LayoutUnit availableWidth, bool lineIsEmpty); 43 BreakingBehavior breakingContextForFloat(LayoutUnit floatLogicalWidth, LayoutUnit availableWidth, bool lineIsEmpty); 52 44 53 45 private: 54 46 55 BreakingBehavior wordBreakingBehavior(const InlineTextItem&, bool lineIsEmpty) const; 56 bool isAtBreakingOpportunity(const InlineItem&); 57 47 BreakingBehavior wordBreakingBehavior(const Vector<LineLayout::Run>&, bool lineIsEmpty) const; 58 48 bool m_hyphenationIsDisabled { true }; 59 49 }; -
trunk/Source/WebCore/layout/inlineformatting/InlineLineLayout.cpp
r251329 r251455 29 29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT) 30 30 31 #include "InlineLineBreaker.h" 31 32 #include "LayoutBox.h" 32 33 #include "TextUtil.h" … … 122 123 123 124 for (auto inlineItemIndex = firstNonPartialIndex; inlineItemIndex < m_lineInput.inlineItems.size(); ++inlineItemIndex) { 125 // FIXME: We should not need to re-measure the dropped, uncommitted content when re-using them on the next line. 124 126 if (placeInlineItem(*m_lineInput.inlineItems[inlineItemIndex]) == IsEndOfLine::Yes) 125 127 return close(); 126 128 } 127 commitPendingContent(); 129 // Check the uncommitted content whether they fit now that we know we are at a commit boundary. 130 if (!m_uncommittedContent.isEmpty()) 131 processUncommittedContent(); 128 132 return close(); 129 133 } … … 142 146 { 143 147 ASSERT(m_committedInlineItemCount || m_lineHasIntrusiveFloat); 148 m_uncommittedContent.reset(); 144 149 if (!m_committedInlineItemCount) 145 150 return LineContent { WTF::nullopt, WTFMove(m_floats), m_line.close(), m_line.lineBox() }; … … 156 161 LineLayout::IsEndOfLine LineLayout::placeInlineItem(const InlineItem& inlineItem) 157 162 { 158 auto availableWidth = m_line.availableWidth() - m_uncommittedContent.width(); 159 auto currentLogicalRight = m_line.lineBox().logicalRight() + m_uncommittedContent.width(); 163 auto currentLogicalRight = m_line.lineBox().logicalRight(); 160 164 auto itemLogicalWidth = inlineItemWidth(formattingContext(), inlineItem, currentLogicalRight); 161 162 // FIXME: Ensure LineContext::trimmableWidth includes uncommitted content if needed.163 165 auto lineIsConsideredEmpty = !m_line.hasContent() && !m_lineHasIntrusiveFloat; 164 auto breakingContext = m_lineBreaker.breakingContext(inlineItem, itemLogicalWidth, { availableWidth, currentLogicalRight, m_line.trailingTrimmableWidth(), lineIsConsideredEmpty }); 165 if (breakingContext.isAtBreakingOpportunity) 166 commitPendingContent(); 167 168 // Content does not fit the current line. 169 if (breakingContext.breakingBehavior == LineBreaker::BreakingBehavior::Wrap) 170 return IsEndOfLine::Yes; 171 172 // Partial content stays on the current line. 173 if (breakingContext.breakingBehavior == LineBreaker::BreakingBehavior::Split) { 174 ASSERT(inlineItem.isText()); 175 auto& inlineTextItem = downcast<InlineTextItem>(inlineItem); 176 auto splitData = TextUtil::split(inlineTextItem.layoutBox(), inlineTextItem.start(), inlineTextItem.length(), itemLogicalWidth, availableWidth, currentLogicalRight); 177 // Construct a partial trailing inline item. 178 ASSERT(!m_trailingPartialInlineTextItem); 179 m_trailingPartialInlineTextItem = inlineTextItem.split(splitData.start, splitData.length); 180 m_uncommittedContent.add(*m_trailingPartialInlineTextItem, splitData.logicalWidth); 181 commitPendingContent(); 182 return IsEndOfLine::Yes; 183 } 184 185 ASSERT(breakingContext.breakingBehavior == LineBreaker::BreakingBehavior::Keep); 166 167 // Floats are special, they are intrusive but they don't really participate in the line layout context. 186 168 if (inlineItem.isFloat()) { 169 // FIXME: It gets a bit more complicated when there's some uncommitted content whether they should be added to the current line 170 // e.g. text_content<div style="float: left"></div>continuous_text_content 171 // Not sure what to do when the float takes up the available space and we've got continuous content. Browser engines don't agree. 172 // Let's just commit the pending content and try placing the float for now. 173 if (!m_uncommittedContent.isEmpty()) { 174 if (processUncommittedContent() == IsEndOfLine::Yes) 175 return IsEndOfLine::Yes; 176 } 177 auto breakingBehavior = LineBreaker().breakingContextForFloat(itemLogicalWidth, m_line.availableWidth() + m_line.trailingTrimmableWidth(), lineIsConsideredEmpty); 178 ASSERT(breakingBehavior != LineBreaker::BreakingBehavior::Split); 179 if (breakingBehavior == LineBreaker::BreakingBehavior::Wrap) 180 return IsEndOfLine::Yes; 181 182 // This float can sit on the current line. 187 183 auto& floatBox = inlineItem.layoutBox(); 188 184 // Shrink available space for current line and move existing inline runs. 189 auto floatBoxWidth = formattingContext().geometryForBox(floatBox).marginBoxWidth(); 190 floatBox.isLeftFloatingPositioned() ? m_line.moveLogicalLeft(floatBoxWidth) : m_line.moveLogicalRight(floatBoxWidth); 185 floatBox.isLeftFloatingPositioned() ? m_line.moveLogicalLeft(itemLogicalWidth) : m_line.moveLogicalRight(itemLogicalWidth); 191 186 m_floats.append(makeWeakPtr(inlineItem)); 192 187 ++m_committedInlineItemCount; … … 194 189 return IsEndOfLine::No; 195 190 } 196 197 m_uncommittedContent.add(inlineItem, itemLogicalWidth); 198 if (breakingContext.isAtBreakingOpportunity) 191 // Explicit line breaks are also special. 192 if (inlineItem.isHardLineBreak()) { 193 auto isEndOfLine = !m_uncommittedContent.isEmpty() ? processUncommittedContent() : IsEndOfLine::No; 194 // When the uncommitted content fits(or the line is empty), add the line break to this line as well. 195 if (isEndOfLine == IsEndOfLine::No) { 196 m_uncommittedContent.add(inlineItem, itemLogicalWidth); 197 commitPendingContent(); 198 } 199 return IsEndOfLine::Yes; 200 } 201 // 202 auto isEndOfLine = IsEndOfLine::No; 203 if (!m_uncommittedContent.isEmpty() && shouldProcessUncommittedContent(inlineItem)) 204 isEndOfLine = processUncommittedContent(); 205 // The current item might fit as well. 206 if (isEndOfLine == IsEndOfLine::No) 207 m_uncommittedContent.add(inlineItem, itemLogicalWidth); 208 return isEndOfLine; 209 } 210 211 LineLayout::IsEndOfLine LineLayout::processUncommittedContent() 212 { 213 // Check if the pending content fits. 214 auto lineIsConsideredEmpty = !m_line.hasContent() && !m_lineHasIntrusiveFloat; 215 auto breakingBehavior = LineBreaker().breakingContext(m_uncommittedContent.runs(), m_uncommittedContent.width(), m_line.availableWidth(), lineIsConsideredEmpty); 216 217 // The uncommitted content can fully, partially fit the current line (commit/partial commit) or not at all (reset). 218 if (breakingBehavior == LineBreaker::BreakingBehavior::Keep) 199 219 commitPendingContent(); 200 201 return inlineItem.isHardLineBreak() ? IsEndOfLine::Yes : IsEndOfLine::No; 202 } 220 else if (breakingBehavior == LineBreaker::BreakingBehavior::Split) 221 ASSERT_NOT_IMPLEMENTED_YET(); 222 else if (breakingBehavior == LineBreaker::BreakingBehavior::Wrap) 223 m_uncommittedContent.reset(); 224 225 return breakingBehavior == LineBreaker::BreakingBehavior::Keep ? IsEndOfLine::No :IsEndOfLine::Yes; 226 } 227 228 bool LineLayout::shouldProcessUncommittedContent(const InlineItem& inlineItem) const 229 { 230 // Figure out if the new incoming content puts the uncommitted content on commit boundary. 231 // e.g. <span>continuous</span> <- uncomitted content -> 232 // [inline container start][text content][inline container end] 233 // An incoming <img> box would enable us to commit the "<span>continuous</span>" content 234 // while additional text content would not. 235 ASSERT(!inlineItem.isFloat() && !inlineItem.isHardLineBreak()); 236 ASSERT(!m_uncommittedContent.isEmpty()); 237 238 auto* lastUncomittedContent = &m_uncommittedContent.runs().last().inlineItem; 239 if (inlineItem.isText()) { 240 // any content' ' -> whitespace is always a commit boundary. 241 if (downcast<InlineTextItem>(inlineItem).isWhitespace()) 242 return true; 243 // <span>text -> the inline container start and the text content form an unbreakable continuous content. 244 if (lastUncomittedContent->isContainerStart()) 245 return false; 246 // </span>text -> need to check what's before the </span>. 247 // text</span>text -> continuous content 248 // <img></span>text -> commit bounday 249 if (lastUncomittedContent->isContainerEnd()) { 250 auto& runs = m_uncommittedContent.runs(); 251 // text</span><span></span></span>text -> check all the way back until we hit either a box or some text 252 for (auto i = m_uncommittedContent.size(); i--;) { 253 auto& previousInlineItem = runs[i].inlineItem; 254 if (previousInlineItem.isContainerStart() || previousInlineItem.isContainerEnd()) 255 continue; 256 ASSERT(previousInlineItem.isText() || previousInlineItem.isBox()); 257 lastUncomittedContent = &previousInlineItem; 258 break; 259 } 260 // Did not find any content (e.g. <span></span>text) 261 if (lastUncomittedContent->isContainerEnd()) 262 return false; 263 } 264 // texttext -> continuous content. 265 // ' 'text -> commit boundary. 266 if (lastUncomittedContent->isText()) 267 return downcast<InlineTextItem>(*lastUncomittedContent).isWhitespace(); 268 // <img>text -> the inline box is on a commit boundary. 269 if (lastUncomittedContent->isBox()) 270 return true; 271 ASSERT_NOT_REACHED(); 272 } 273 274 if (inlineItem.isBox()) { 275 // <span><img> -> the inline container start and the content form an unbreakable continuous content. 276 if (lastUncomittedContent->isContainerStart()) 277 return false; 278 // </span><img> -> ok to commit the </span>. 279 if (lastUncomittedContent->isContainerEnd()) 280 return true; 281 // <img>text and <img><img> -> these combinations are ok to commit. 282 if (lastUncomittedContent->isText() || lastUncomittedContent->isBox()) 283 return true; 284 ASSERT_NOT_REACHED(); 285 } 286 287 if (inlineItem.isContainerStart() || inlineItem.isContainerEnd()) { 288 // <span><span> or </span><span> -> can't commit the previous content yet. 289 if (lastUncomittedContent->isContainerStart() || lastUncomittedContent->isContainerEnd()) 290 return false; 291 // ' '<span> -> let's commit the whitespace 292 // text<span> -> but not yet the non-whitespace; we need to know what comes next (e.g. text<span>text or text<span><img>). 293 if (lastUncomittedContent->isText()) 294 return downcast<InlineTextItem>(*lastUncomittedContent).isWhitespace(); 295 // <img><span> -> it's ok to commit the inline box content. 296 // <img></span> -> the inline box and the closing inline container form an unbreakable continuous content. 297 if (lastUncomittedContent->isBox()) 298 return inlineItem.isContainerStart(); 299 ASSERT_NOT_REACHED(); 300 } 301 302 ASSERT_NOT_REACHED(); 303 return true; 304 } 305 203 306 204 307 } -
trunk/Source/WebCore/layout/inlineformatting/InlineLineLayout.h
r250490 r251455 29 29 30 30 #include "InlineLine.h" 31 #include "InlineLineBreaker.h"32 31 #include <wtf/IsoMalloc.h> 33 32 … … 72 71 }; 73 72 73 struct Run { 74 const InlineItem& inlineItem; 75 LayoutUnit logicalWidth; 76 }; 77 74 78 private: 75 79 const InlineFormattingContext& formattingContext() const { return m_inlineFormattingContext; } … … 78 82 void commitPendingContent(); 79 83 LineContent close(); 84 bool shouldProcessUncommittedContent(const InlineItem&) const; 85 IsEndOfLine processUncommittedContent(); 80 86 81 87 struct UncommittedContent { 82 struct Run {83 const InlineItem& inlineItem;84 LayoutUnit logicalWidth;85 };86 88 void add(const InlineItem&, LayoutUnit logicalWidth); 87 89 void reset(); 88 90 89 Vector<Run> runs() { return m_uncommittedRuns; } 91 Vector<Run>& runs() { return m_uncommittedRuns; } 92 const Vector<Run>& runs() const { return m_uncommittedRuns; } 90 93 bool isEmpty() const { return m_uncommittedRuns.isEmpty(); } 91 94 unsigned size() const { return m_uncommittedRuns.size(); } … … 100 103 const LineInput& m_lineInput; 101 104 Line m_line; 102 LineBreaker m_lineBreaker;103 105 bool m_lineHasIntrusiveFloat { false }; 104 106 UncommittedContent m_uncommittedContent;
Note: See TracChangeset
for help on using the changeset viewer.