1 | /* |
2 | * Copyright (C) 2013 Apple Inc. All rights reserved. |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * 1. Redistributions of source code must retain the above copyright |
8 | * notice, this list of conditions and the following disclaimer. |
9 | * 2. Redistributions in binary form must reproduce the above copyright |
10 | * notice, this list of conditions and the following disclaimer in the |
11 | * documentation and/or other materials provided with the distribution. |
12 | * |
13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
23 | * THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include "config.h" |
27 | #include "SimpleLineLayout.h" |
28 | |
29 | #include "DocumentMarkerController.h" |
30 | #include "FontCache.h" |
31 | #include "Frame.h" |
32 | #include "GraphicsContext.h" |
33 | #include "HTMLTextFormControlElement.h" |
34 | #include "HitTestLocation.h" |
35 | #include "HitTestRequest.h" |
36 | #include "HitTestResult.h" |
37 | #include "Hyphenation.h" |
38 | #include "InlineTextBox.h" |
39 | #include "LineWidth.h" |
40 | #include "Logging.h" |
41 | #include "PaintInfo.h" |
42 | #include "RenderBlockFlow.h" |
43 | #include "RenderChildIterator.h" |
44 | #include "RenderFragmentedFlow.h" |
45 | #include "RenderLineBreak.h" |
46 | #include "RenderMultiColumnFlow.h" |
47 | #include "RenderStyle.h" |
48 | #include "RenderText.h" |
49 | #include "RenderTextControl.h" |
50 | #include "RenderView.h" |
51 | #include "Settings.h" |
52 | #include "SimpleLineLayoutFlowContents.h" |
53 | #include "SimpleLineLayoutFunctions.h" |
54 | #include "SimpleLineLayoutResolver.h" |
55 | #include "SimpleLineLayoutTextFragmentIterator.h" |
56 | #include "Text.h" |
57 | #include "TextPaintStyle.h" |
58 | #include <pal/Logging.h> |
59 | |
60 | namespace WebCore { |
61 | namespace SimpleLineLayout { |
62 | |
63 | #ifndef NDEBUG |
64 | #define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \ |
65 | reasons |= reason; \ |
66 | if (includeReasons == IncludeReasons::First) \ |
67 | return reasons; \ |
68 | } |
69 | #else |
70 | #define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \ |
71 | ASSERT_UNUSED(includeReasons, includeReasons == IncludeReasons::First); \ |
72 | reasons |= reason; \ |
73 | return reasons; \ |
74 | } |
75 | #endif |
76 | |
77 | |
78 | template <typename CharacterType> AvoidanceReasonFlags canUseForCharacter(CharacterType, bool textIsJustified, IncludeReasons); |
79 | |
80 | template<> AvoidanceReasonFlags canUseForCharacter(UChar character, bool textIsJustified, IncludeReasons includeReasons) |
81 | { |
82 | AvoidanceReasonFlags reasons = { }; |
83 | if (textIsJustified) { |
84 | // Include characters up to Latin Extended-B and some punctuation range when text is justified. |
85 | bool isLatinIncludingExtendedB = character <= 0x01FF; |
86 | bool isPunctuationRange = character >= 0x2010 && character <= 0x2027; |
87 | if (!(isLatinIncludingExtendedB || isPunctuationRange)) |
88 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasJustifiedNonLatinText, reasons, includeReasons); |
89 | } |
90 | |
91 | if (U16_IS_SURROGATE(character)) |
92 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSurrogatePair, reasons, includeReasons); |
93 | |
94 | UCharDirection direction = u_charDirection(character); |
95 | if (direction == U_RIGHT_TO_LEFT || direction == U_RIGHT_TO_LEFT_ARABIC |
96 | || direction == U_RIGHT_TO_LEFT_EMBEDDING || direction == U_RIGHT_TO_LEFT_OVERRIDE |
97 | || direction == U_LEFT_TO_RIGHT_EMBEDDING || direction == U_LEFT_TO_RIGHT_OVERRIDE |
98 | || direction == U_POP_DIRECTIONAL_FORMAT || direction == U_BOUNDARY_NEUTRAL) |
99 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasDirectionCharacter, reasons, includeReasons); |
100 | |
101 | return reasons; |
102 | } |
103 | |
104 | template<> AvoidanceReasonFlags canUseForCharacter(LChar, bool, IncludeReasons) |
105 | { |
106 | return { }; |
107 | } |
108 | |
109 | template <typename CharacterType> |
110 | static AvoidanceReasonFlags canUseForText(const CharacterType* text, unsigned length, const FontCascade& fontCascade, Optional<float> lineHeightConstraint, |
111 | bool textIsJustified, IncludeReasons includeReasons) |
112 | { |
113 | AvoidanceReasonFlags reasons = { }; |
114 | auto& primaryFont = fontCascade.primaryFont(); |
115 | auto& fontMetrics = primaryFont.fontMetrics(); |
116 | auto availableSpaceForGlyphAscent = fontMetrics.ascent(); |
117 | auto availableSpaceForGlyphDescent = fontMetrics.descent(); |
118 | if (lineHeightConstraint) { |
119 | auto lineHeightPadding = *lineHeightConstraint - fontMetrics.height(); |
120 | availableSpaceForGlyphAscent += lineHeightPadding / 2; |
121 | availableSpaceForGlyphDescent += lineHeightPadding / 2; |
122 | } |
123 | |
124 | for (unsigned i = 0; i < length; ++i) { |
125 | auto character = text[i]; |
126 | if (FontCascade::treatAsSpace(character)) |
127 | continue; |
128 | |
129 | if (character == softHyphen) |
130 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSoftHyphen, reasons, includeReasons); |
131 | |
132 | auto characterReasons = canUseForCharacter(character, textIsJustified, includeReasons); |
133 | if (characterReasons != NoReason) |
134 | SET_REASON_AND_RETURN_IF_NEEDED(characterReasons, reasons, includeReasons); |
135 | |
136 | auto glyphData = fontCascade.glyphDataForCharacter(character, false); |
137 | if (!glyphData.isValid() || glyphData.font != &primaryFont) |
138 | SET_REASON_AND_RETURN_IF_NEEDED(FlowPrimaryFontIsInsufficient, reasons, includeReasons); |
139 | |
140 | if (lineHeightConstraint) { |
141 | auto bounds = primaryFont.boundsForGlyph(glyphData.glyph); |
142 | if (ceilf(-bounds.y()) > availableSpaceForGlyphAscent || ceilf(bounds.maxY()) > availableSpaceForGlyphDescent) |
143 | SET_REASON_AND_RETURN_IF_NEEDED(FlowFontHasOverflowGlyph, reasons, includeReasons); |
144 | } |
145 | } |
146 | return reasons; |
147 | } |
148 | |
149 | static AvoidanceReasonFlags canUseForText(StringView text, const FontCascade& fontCascade, Optional<float> lineHeightConstraint, bool textIsJustified, IncludeReasons includeReasons) |
150 | { |
151 | if (text.is8Bit()) |
152 | return canUseForText(text.characters8(), text.length(), fontCascade, lineHeightConstraint, textIsJustified, includeReasons); |
153 | return canUseForText(text.characters16(), text.length(), fontCascade, lineHeightConstraint, textIsJustified, includeReasons); |
154 | } |
155 | |
156 | static AvoidanceReasonFlags canUseForFontAndText(const RenderBlockFlow& flow, IncludeReasons includeReasons) |
157 | { |
158 | AvoidanceReasonFlags reasons = { }; |
159 | // We assume that all lines have metrics based purely on the primary font. |
160 | const auto& style = flow.style(); |
161 | auto& fontCascade = style.fontCascade(); |
162 | if (fontCascade.primaryFont().isInterstitial()) |
163 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsMissingPrimaryFont, reasons, includeReasons); |
164 | Optional<float> lineHeightConstraint; |
165 | if (style.lineBoxContain() & LineBoxContainGlyphs) |
166 | lineHeightConstraint = lineHeightFromFlow(flow).toFloat(); |
167 | bool flowIsJustified = style.textAlign() == TextAlignMode::Justify; |
168 | for (const auto& textRenderer : childrenOfType<RenderText>(flow)) { |
169 | // FIXME: Do not return until after checking all children. |
170 | if (textRenderer.text().isEmpty()) |
171 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsEmpty, reasons, includeReasons); |
172 | if (textRenderer.isCombineText()) |
173 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsCombineText, reasons, includeReasons); |
174 | if (textRenderer.isCounter()) |
175 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderCounter, reasons, includeReasons); |
176 | if (textRenderer.isQuote()) |
177 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderQuote, reasons, includeReasons); |
178 | if (textRenderer.isTextFragment()) |
179 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsTextFragment, reasons, includeReasons); |
180 | if (textRenderer.isSVGInlineText()) |
181 | SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsSVGInlineText, reasons, includeReasons); |
182 | if (!textRenderer.canUseSimpleFontCodePath()) { |
183 | // No need to check the code path at this point. We already know it can't be simple. |
184 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasComplexFontCodePath, reasons, includeReasons); |
185 | } else { |
186 | TextRun run(String(textRenderer.text())); |
187 | run.setCharacterScanForCodePath(false); |
188 | if (style.fontCascade().codePath(run) != FontCascade::Simple) |
189 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasComplexFontCodePath, reasons, includeReasons); |
190 | } |
191 | |
192 | auto textReasons = canUseForText(textRenderer.stringView(), fontCascade, lineHeightConstraint, flowIsJustified, includeReasons); |
193 | if (textReasons != NoReason) |
194 | SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons); |
195 | } |
196 | return reasons; |
197 | } |
198 | |
199 | static AvoidanceReasonFlags canUseForStyle(const RenderStyle& style, IncludeReasons includeReasons) |
200 | { |
201 | AvoidanceReasonFlags reasons = { }; |
202 | if (style.textOverflow() == TextOverflow::Ellipsis) |
203 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons); |
204 | if (style.textUnderlinePosition() != TextUnderlinePosition::Auto || !style.textUnderlineOffset().isAuto() || !style.textDecorationThickness().isAuto()) |
205 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedUnderlineDecoration, reasons, includeReasons); |
206 | // Non-visible overflow should be pretty easy to support. |
207 | if (style.overflowX() != Overflow::Visible || style.overflowY() != Overflow::Visible) |
208 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOverflowNotVisible, reasons, includeReasons); |
209 | if (!style.isLeftToRightDirection()) |
210 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotLTR, reasons, includeReasons); |
211 | if (!(style.lineBoxContain() & LineBoxContainBlock)) |
212 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBoxContainProperty, reasons, includeReasons); |
213 | if (style.writingMode() != TopToBottomWritingMode) |
214 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotTopToBottom, reasons, includeReasons); |
215 | if (style.lineBreak() != LineBreak::Auto) |
216 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBreak, reasons, includeReasons); |
217 | if (style.unicodeBidi() != UBNormal) |
218 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonNormalUnicodeBiDi, reasons, includeReasons); |
219 | if (style.rtlOrdering() != Order::Logical) |
220 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasRTLOrdering, reasons, includeReasons); |
221 | if (style.lineAlign() != LineAlign::None) |
222 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineAlignEdges, reasons, includeReasons); |
223 | if (style.lineSnap() != LineSnap::None) |
224 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineSnap, reasons, includeReasons); |
225 | if (style.textEmphasisFill() != TextEmphasisFill::Filled || style.textEmphasisMark() != TextEmphasisMark::None) |
226 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextEmphasisFillOrMark, reasons, includeReasons); |
227 | if (style.textShadow()) |
228 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextShadow, reasons, includeReasons); |
229 | if (style.hasPseudoStyle(PseudoId::FirstLine)) |
230 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons); |
231 | if (style.hasPseudoStyle(PseudoId::FirstLetter)) |
232 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLetter, reasons, includeReasons); |
233 | if (style.hasTextCombine()) |
234 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextCombine, reasons, includeReasons); |
235 | if (style.backgroundClip() == FillBox::Text) |
236 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextFillBox, reasons, includeReasons); |
237 | if (style.borderFit() == BorderFit::Lines) |
238 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasBorderFitLines, reasons, includeReasons); |
239 | if (style.lineBreak() != LineBreak::Auto) |
240 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonAutoLineBreak, reasons, includeReasons); |
241 | if (style.nbspMode() != NBSPMode::Normal) |
242 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasWebKitNBSPMode, reasons, includeReasons); |
243 | if (style.hyphens() == Hyphens::Auto) { |
244 | auto textReasons = canUseForText(style.hyphenString(), style.fontCascade(), WTF::nullopt, false, includeReasons); |
245 | if (textReasons != NoReason) |
246 | SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons); |
247 | } |
248 | return reasons; |
249 | } |
250 | |
251 | AvoidanceReasonFlags canUseForWithReason(const RenderBlockFlow& flow, IncludeReasons includeReasons) |
252 | { |
253 | #ifndef NDEBUG |
254 | static std::once_flag onceFlag; |
255 | std::call_once(onceFlag, [] { |
256 | PAL::registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutCoverage" , WTF::Function<void()> { printSimpleLineLayoutCoverage }); |
257 | PAL::registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutReasons" , WTF::Function<void()> { printSimpleLineLayoutBlockList }); |
258 | PAL::registerNotifyCallback("com.apple.WebKit.toggleSimpleLineLayout" , WTF::Function<void()> { toggleSimpleLineLayout }); |
259 | }); |
260 | #endif |
261 | AvoidanceReasonFlags reasons = { }; |
262 | if (!flow.settings().simpleLineLayoutEnabled()) |
263 | SET_REASON_AND_RETURN_IF_NEEDED(FeatureIsDisabled, reasons, includeReasons); |
264 | if (!flow.parent()) |
265 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoParent, reasons, includeReasons); |
266 | if (!flow.firstChild()) |
267 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoChild, reasons, includeReasons); |
268 | if (flow.fragmentedFlowState() != RenderObject::NotInsideFragmentedFlow) { |
269 | auto* fragmentedFlow = flow.enclosingFragmentedFlow(); |
270 | if (!is<RenderMultiColumnFlow>(fragmentedFlow)) |
271 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsInsideANonMultiColumnThread, reasons, includeReasons); |
272 | auto& columnThread = downcast<RenderMultiColumnFlow>(*fragmentedFlow); |
273 | if (columnThread.parent() != &flow.view()) |
274 | SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowIsNotTopLevel, reasons, includeReasons); |
275 | if (columnThread.hasColumnSpanner()) |
276 | SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowHasColumnSpanner, reasons, includeReasons); |
277 | auto& style = flow.style(); |
278 | if (style.verticalAlign() != VerticalAlign::Baseline) |
279 | SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowVerticalAlign, reasons, includeReasons); |
280 | if (style.isFloating()) |
281 | SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowIsFloating, reasons, includeReasons); |
282 | } |
283 | if (!flow.isHorizontalWritingMode()) |
284 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHorizonalWritingMode, reasons, includeReasons); |
285 | if (flow.hasOutline()) |
286 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOutline, reasons, includeReasons); |
287 | if (flow.isRubyText() || flow.isRubyBase()) |
288 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsRuby, reasons, includeReasons); |
289 | if (!flow.style().hangingPunctuation().isEmpty()) |
290 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHangingPunctuation, reasons, includeReasons); |
291 | |
292 | // Printing does pagination without a flow thread. |
293 | if (flow.document().paginated()) |
294 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsPaginated, reasons, includeReasons); |
295 | if (flow.firstLineBlock()) |
296 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons); |
297 | if (flow.isAnonymousBlock() && flow.parent()->style().textOverflow() == TextOverflow::Ellipsis) |
298 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons); |
299 | if (flow.parent()->isDeprecatedFlexibleBox()) |
300 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIsDepricatedFlexBox, reasons, includeReasons); |
301 | // FIXME: Placeholders do something strange. |
302 | if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement()) |
303 | SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsPlaceholderElement, reasons, includeReasons); |
304 | // FIXME: Implementation of wrap=hard looks into lineboxes. |
305 | if (flow.parent()->isTextArea() && flow.parent()->element()->hasAttributeWithoutSynchronization(HTMLNames::wrapAttr)) |
306 | SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsTextAreaWithWrapping, reasons, includeReasons); |
307 | // This currently covers <blockflow>#text</blockflow>, <blockflow>#text<br></blockflow> and mutiple (sibling) RenderText cases. |
308 | // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover. |
309 | for (const auto* child = flow.firstChild(); child;) { |
310 | if (child->selectionState() != RenderObject::SelectionNone) |
311 | SET_REASON_AND_RETURN_IF_NEEDED(FlowChildIsSelected, reasons, includeReasons); |
312 | if (is<RenderText>(*child)) { |
313 | const auto& renderText = downcast<RenderText>(*child); |
314 | if (renderText.textNode() && !renderText.document().markers().markersFor(*renderText.textNode()).isEmpty()) |
315 | SET_REASON_AND_RETURN_IF_NEEDED(FlowIncludesDocumentMarkers, reasons, includeReasons); |
316 | child = child->nextSibling(); |
317 | continue; |
318 | } |
319 | if (is<RenderLineBreak>(child) && !downcast<RenderLineBreak>(*child).isWBR() && child->style().clear() == Clear::None) { |
320 | child = child->nextSibling(); |
321 | continue; |
322 | } |
323 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonSupportedChild, reasons, includeReasons); |
324 | break; |
325 | } |
326 | auto styleReasons = canUseForStyle(flow.style(), includeReasons); |
327 | if (styleReasons != NoReason) |
328 | SET_REASON_AND_RETURN_IF_NEEDED(styleReasons, reasons, includeReasons); |
329 | // We can't use the code path if any lines would need to be shifted below floats. This is because we don't keep per-line y coordinates. |
330 | if (flow.containsFloats()) { |
331 | float minimumWidthNeeded = std::numeric_limits<float>::max(); |
332 | for (const auto& textRenderer : childrenOfType<RenderText>(flow)) { |
333 | minimumWidthNeeded = std::min(minimumWidthNeeded, textRenderer.minLogicalWidth()); |
334 | |
335 | for (auto& floatingObject : *flow.floatingObjectSet()) { |
336 | ASSERT(floatingObject); |
337 | // if a float has a shape, we cannot tell if content will need to be shifted until after we lay it out, |
338 | // since the amount of space is not uniform for the height of the float. |
339 | if (floatingObject->renderer().shapeOutsideInfo()) |
340 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons); |
341 | float availableWidth = flow.availableLogicalWidthForLine(floatingObject->y(), DoNotIndentText); |
342 | if (availableWidth < minimumWidthNeeded) |
343 | SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons); |
344 | } |
345 | } |
346 | } |
347 | auto fontAndTextReasons = canUseForFontAndText(flow, includeReasons); |
348 | if (fontAndTextReasons != NoReason) |
349 | SET_REASON_AND_RETURN_IF_NEEDED(fontAndTextReasons, reasons, includeReasons); |
350 | return reasons; |
351 | } |
352 | |
353 | bool canUseFor(const RenderBlockFlow& flow) |
354 | { |
355 | return canUseForWithReason(flow, IncludeReasons::First) == NoReason; |
356 | } |
357 | |
358 | static float computeLineLeft(TextAlignMode textAlign, float availableWidth, float committedWidth, float logicalLeftOffset) |
359 | { |
360 | float remainingWidth = availableWidth - committedWidth; |
361 | float left = logicalLeftOffset; |
362 | switch (textAlign) { |
363 | case TextAlignMode::Left: |
364 | case TextAlignMode::WebKitLeft: |
365 | case TextAlignMode::Start: |
366 | return left; |
367 | case TextAlignMode::Right: |
368 | case TextAlignMode::WebKitRight: |
369 | case TextAlignMode::End: |
370 | return left + std::max<float>(remainingWidth, 0); |
371 | case TextAlignMode::Center: |
372 | case TextAlignMode::WebKitCenter: |
373 | return left + std::max<float>(remainingWidth / 2, 0); |
374 | case TextAlignMode::Justify: |
375 | ASSERT_NOT_REACHED(); |
376 | break; |
377 | } |
378 | ASSERT_NOT_REACHED(); |
379 | return 0; |
380 | } |
381 | |
382 | static void revertAllRunsOnCurrentLine(Layout::RunVector& runs) |
383 | { |
384 | while (!runs.isEmpty() && !runs.last().isEndOfLine) |
385 | runs.removeLast(); |
386 | } |
387 | |
388 | static void revertRuns(Layout::RunVector& runs, unsigned positionToRevertTo, float width) |
389 | { |
390 | while (runs.size()) { |
391 | auto& lastRun = runs.last(); |
392 | if (lastRun.end <= positionToRevertTo) |
393 | break; |
394 | if (lastRun.start >= positionToRevertTo) { |
395 | // Revert this run completely. |
396 | width -= (lastRun.logicalRight - lastRun.logicalLeft); |
397 | runs.removeLast(); |
398 | } else { |
399 | lastRun.logicalRight -= width; |
400 | width = 0; |
401 | lastRun.end = positionToRevertTo; |
402 | // Partial removal. |
403 | break; |
404 | } |
405 | } |
406 | } |
407 | |
408 | class LineState { |
409 | public: |
410 | void setAvailableWidth(float width) { m_availableWidth = width; } |
411 | void setCollapedWhitespaceWidth(float width) { m_collapsedWhitespaceWidth = width; } |
412 | void setLogicalLeftOffset(float offset) { m_logicalLeftOffset = offset; } |
413 | void setOverflowedFragment(const TextFragmentIterator::TextFragment& fragment) { m_overflowedFragment = fragment; } |
414 | void setNeedsAllFragments() |
415 | { |
416 | ASSERT(!m_fragments); |
417 | m_fragments.emplace(); |
418 | } |
419 | void setHyphenationDisabled() { m_hyphenationDisabled = true; } |
420 | bool isHyphenationDisabled() const { return m_hyphenationDisabled; } |
421 | |
422 | float availableWidth() const { return m_availableWidth; } |
423 | float logicalLeftOffset() const { return m_logicalLeftOffset; } |
424 | const TextFragmentIterator::TextFragment& overflowedFragment() const { return m_overflowedFragment; } |
425 | bool hasTrailingWhitespace() const { return m_lastFragment.type() == TextFragmentIterator::TextFragment::Whitespace && m_lastFragment.length() > 0; } |
426 | bool hasWhitespaceFragments() const { return m_lastWhitespaceFragment != WTF::nullopt; } |
427 | TextFragmentIterator::TextFragment lastFragment() const { return m_lastFragment; } |
428 | bool isWhitespaceOnly() const { return m_trailingWhitespaceWidth && m_runsWidth == m_trailingWhitespaceWidth; } |
429 | bool fits(float ) const { return m_availableWidth >= m_runsWidth + extra; } |
430 | bool firstCharacterFits() const { return m_firstCharacterFits; } |
431 | float width() const { return m_runsWidth; } |
432 | std::pair<unsigned, bool> expansionOpportunityCount(unsigned from, unsigned to) const |
433 | { |
434 | ASSERT(m_fragments); |
435 | // linebreak runs are special. |
436 | if (from == to) |
437 | return std::make_pair(0, false); |
438 | unsigned expansionOpportunityCount = 0; |
439 | auto previousFragmentType = TextFragmentIterator::TextFragment::ContentEnd; |
440 | for (const auto& fragment : *m_fragments) { |
441 | if (fragment.end() <= from) |
442 | continue; |
443 | auto currentFragmentType = fragment.type(); |
444 | auto expansionOpportunity = this->expansionOpportunity(currentFragmentType, previousFragmentType); |
445 | if (expansionOpportunity) |
446 | ++expansionOpportunityCount; |
447 | previousFragmentType = currentFragmentType; |
448 | if (fragment.end() >= to) |
449 | return std::make_pair(expansionOpportunityCount, expansionOpportunity); |
450 | } |
451 | ASSERT_NOT_REACHED(); |
452 | return std::make_pair(expansionOpportunityCount, false); |
453 | } |
454 | |
455 | bool isEmpty() const |
456 | { |
457 | if (!m_lastFragment.isValid()) |
458 | return true; |
459 | if (!m_lastCompleteFragment.isEmpty()) |
460 | return false; |
461 | return m_lastFragment.overlapsToNextRenderer(); |
462 | } |
463 | |
464 | static inline unsigned endPositionForCollapsedFragment(const TextFragmentIterator::TextFragment& fragment) |
465 | { |
466 | return fragment.isCollapsed() ? fragment.start() + 1 : fragment.end(); |
467 | } |
468 | |
469 | void appendFragmentAndCreateRunIfNeeded(const TextFragmentIterator::TextFragment& fragment, Layout::RunVector& runs) |
470 | { |
471 | // Adjust end position while collapsing. |
472 | unsigned endPosition = endPositionForCollapsedFragment(fragment); |
473 | // New line needs new run. |
474 | if (!m_runsWidth) { |
475 | ASSERT(!m_uncompletedWidth); |
476 | runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false, fragment.hasHyphen())); |
477 | } else { |
478 | // Advance last completed fragment when the previous fragment is all set (including multiple parts across renderers) |
479 | if ((m_lastFragment.type() != fragment.type()) || !m_lastFragment.overlapsToNextRenderer()) { |
480 | m_lastCompleteFragment = m_lastFragment; |
481 | m_uncompletedWidth = fragment.width(); |
482 | } else |
483 | m_uncompletedWidth += fragment.width(); |
484 | // Collapse neighbouring whitespace, if they are across multiple renderers and are not collapsed yet. |
485 | if (m_lastFragment.isCollapsible() && fragment.isCollapsible()) { |
486 | ASSERT(m_lastFragment.isLastInRenderer()); |
487 | if (!m_lastFragment.isCollapsed()) { |
488 | // Line width needs to be adjusted so that now it takes collapsing into consideration. |
489 | m_runsWidth -= (m_lastFragment.width() - m_collapsedWhitespaceWidth); |
490 | } |
491 | // This fragment is collapsed completely. No run is needed. |
492 | return; |
493 | } |
494 | if (m_lastFragment.isLastInRenderer() || m_lastFragment.isCollapsed()) |
495 | runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false, fragment.hasHyphen())); |
496 | else { |
497 | Run& lastRun = runs.last(); |
498 | lastRun.end = endPosition; |
499 | lastRun.logicalRight += fragment.width(); |
500 | ASSERT(!lastRun.hasHyphen); |
501 | lastRun.hasHyphen = fragment.hasHyphen(); |
502 | } |
503 | } |
504 | m_runsWidth += fragment.width(); |
505 | m_lastFragment = fragment; |
506 | if (m_fragments) |
507 | (*m_fragments).append(fragment); |
508 | |
509 | if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) { |
510 | m_trailingWhitespaceWidth += fragment.width(); |
511 | m_lastWhitespaceFragment = fragment; |
512 | } else { |
513 | m_trailingWhitespaceWidth = 0; |
514 | m_lastNonWhitespaceFragment = fragment; |
515 | } |
516 | |
517 | if (!m_firstCharacterFits) |
518 | m_firstCharacterFits = fragment.start() + 1 > endPosition || m_runsWidth <= m_availableWidth; |
519 | } |
520 | |
521 | TextFragmentIterator::TextFragment revertToLastCompleteFragment(Layout::RunVector& runs) |
522 | { |
523 | if (!m_uncompletedWidth) { |
524 | ASSERT(m_lastFragment == m_lastCompleteFragment); |
525 | return m_lastFragment; |
526 | } |
527 | ASSERT(m_lastFragment.isValid()); |
528 | m_runsWidth -= m_uncompletedWidth; |
529 | revertRuns(runs, endPositionForCollapsedFragment(m_lastCompleteFragment), m_uncompletedWidth); |
530 | m_uncompletedWidth = 0; |
531 | ASSERT(m_lastCompleteFragment.isValid()); |
532 | return m_lastCompleteFragment; |
533 | } |
534 | |
535 | void removeTrailingWhitespace(Layout::RunVector& runs) |
536 | { |
537 | if (m_lastFragment.type() != TextFragmentIterator::TextFragment::Whitespace) |
538 | return; |
539 | if (m_lastNonWhitespaceFragment) { |
540 | auto needsReverting = m_lastNonWhitespaceFragment->end() != m_lastFragment.end(); |
541 | // Trailing whitespace fragment might actually have zero length. |
542 | ASSERT(needsReverting || !m_trailingWhitespaceWidth); |
543 | if (needsReverting) { |
544 | revertRuns(runs, m_lastNonWhitespaceFragment->end(), m_trailingWhitespaceWidth); |
545 | m_runsWidth -= m_trailingWhitespaceWidth; |
546 | } |
547 | m_trailingWhitespaceWidth = 0; |
548 | m_lastFragment = *m_lastNonWhitespaceFragment; |
549 | return; |
550 | } |
551 | // This line is all whitespace. |
552 | revertAllRunsOnCurrentLine(runs); |
553 | m_runsWidth = 0; |
554 | m_trailingWhitespaceWidth = 0; |
555 | // FIXME: Make m_lastFragment optional. |
556 | m_lastFragment = TextFragmentIterator::TextFragment(); |
557 | } |
558 | |
559 | private: |
560 | bool expansionOpportunity(TextFragmentIterator::TextFragment::Type currentFragmentType, TextFragmentIterator::TextFragment::Type previousFragmentType) const |
561 | { |
562 | return (currentFragmentType == TextFragmentIterator::TextFragment::Whitespace |
563 | || (currentFragmentType == TextFragmentIterator::TextFragment::NonWhitespace && previousFragmentType == TextFragmentIterator::TextFragment::NonWhitespace)); |
564 | } |
565 | |
566 | float m_availableWidth { 0 }; |
567 | float m_logicalLeftOffset { 0 }; |
568 | float m_runsWidth { 0 }; |
569 | TextFragmentIterator::TextFragment m_overflowedFragment; |
570 | TextFragmentIterator::TextFragment m_lastFragment; |
571 | Optional<TextFragmentIterator::TextFragment> m_lastNonWhitespaceFragment; |
572 | Optional<TextFragmentIterator::TextFragment> m_lastWhitespaceFragment; |
573 | TextFragmentIterator::TextFragment m_lastCompleteFragment; |
574 | float m_uncompletedWidth { 0 }; |
575 | float m_trailingWhitespaceWidth { 0 }; // Use this to remove trailing whitespace without re-mesuring the text. |
576 | float m_collapsedWhitespaceWidth { 0 }; |
577 | // Having one character on the line does not necessarily mean it actually fits. |
578 | // First character of the first fragment might be forced on to the current line even if it does not fit. |
579 | bool m_firstCharacterFits { false }; |
580 | bool m_hyphenationDisabled { false }; |
581 | Optional<Vector<TextFragmentIterator::TextFragment, 30>> m_fragments; |
582 | }; |
583 | |
584 | static bool preWrap(const TextFragmentIterator::Style& style) |
585 | { |
586 | return style.wrapLines && !style.collapseWhitespace; |
587 | } |
588 | |
589 | static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& runs, const TextFragmentIterator& textFragmentIterator) |
590 | { |
591 | if (!lineState.hasTrailingWhitespace()) |
592 | return; |
593 | // Remove collapsed whitespace, or non-collapsed pre-wrap whitespace, unless it's the only content on the line -so removing the whitesapce |
594 | // would produce an empty line. |
595 | const auto& style = textFragmentIterator.style(); |
596 | bool collapseWhitespace = style.collapseWhitespace || (!style.breakSpaces && preWrap(style)); |
597 | if (!collapseWhitespace) |
598 | return; |
599 | if (preWrap(style) && lineState.isWhitespaceOnly()) |
600 | return; |
601 | lineState.removeTrailingWhitespace(runs); |
602 | } |
603 | |
604 | static void updateLineConstrains(const RenderBlockFlow& flow, LineState& line, const LineState& previousLine, unsigned& numberOfPrecedingLinesWithHyphen, const TextFragmentIterator::Style& style, bool isFirstLine) |
605 | { |
606 | bool shouldApplyTextIndent = !flow.isAnonymous() || flow.parent()->firstChild() == &flow; |
607 | LayoutUnit height = flow.logicalHeight(); |
608 | LayoutUnit logicalHeight = flow.minLineHeightForReplacedRenderer(false, 0); |
609 | line.setLogicalLeftOffset(flow.logicalLeftOffsetForLine(height, DoNotIndentText, logicalHeight) + (shouldApplyTextIndent && isFirstLine ? flow.textIndentOffset() : 0_lu)); |
610 | float logicalRightOffset = flow.logicalRightOffsetForLine(height, DoNotIndentText, logicalHeight); |
611 | line.setAvailableWidth(std::max<float>(0, logicalRightOffset - line.logicalLeftOffset())); |
612 | if (style.textAlign == TextAlignMode::Justify) |
613 | line.setNeedsAllFragments(); |
614 | numberOfPrecedingLinesWithHyphen = (previousLine.isEmpty() || !previousLine.lastFragment().hasHyphen()) ? 0 : numberOfPrecedingLinesWithHyphen + 1; |
615 | if (style.hyphenLimitLines && numberOfPrecedingLinesWithHyphen >= *style.hyphenLimitLines) |
616 | line.setHyphenationDisabled(); |
617 | line.setCollapedWhitespaceWidth(style.font.spaceWidth() + style.wordSpacing); |
618 | } |
619 | |
620 | struct SplitFragmentData { |
621 | unsigned position; |
622 | float width; |
623 | }; |
624 | static Optional<unsigned> hyphenPositionForFragment(SplitFragmentData splitData, const TextFragmentIterator::TextFragment& fragmentToSplit, |
625 | const LineState& line, const TextFragmentIterator& textFragmentIterator, float availableWidth) |
626 | { |
627 | auto& style = textFragmentIterator.style(); |
628 | if (!style.shouldHyphenate || line.isHyphenationDisabled()) |
629 | return WTF::nullopt; |
630 | |
631 | // FIXME: This is a workaround for webkit.org/b/169613. See maxPrefixWidth computation in tryHyphenating(). |
632 | // It does not work properly with non-collapsed leading tabs when font is enlarged. |
633 | auto adjustedAvailableWidth = availableWidth - style.hyphenStringWidth; |
634 | if (!line.isEmpty()) |
635 | adjustedAvailableWidth += style.font.spaceWidth(); |
636 | if (!enoughWidthForHyphenation(adjustedAvailableWidth, style.font.pixelSize())) |
637 | return WTF::nullopt; |
638 | |
639 | // We might be able to fit the hyphen at the split position. |
640 | auto splitPositionWithHyphen = splitData.position; |
641 | // Find a splitting position where hyphen surely fits. |
642 | unsigned start = fragmentToSplit.start(); |
643 | auto leftSideWidth = splitData.width; |
644 | while (leftSideWidth + style.hyphenStringWidth > availableWidth) { |
645 | if (--splitPositionWithHyphen <= start) |
646 | return WTF::nullopt; // No space for hyphen. |
647 | leftSideWidth -= textFragmentIterator.textWidth(splitPositionWithHyphen, splitPositionWithHyphen + 1, 0); |
648 | } |
649 | ASSERT(splitPositionWithHyphen > start); |
650 | return textFragmentIterator.lastHyphenPosition(fragmentToSplit, splitPositionWithHyphen + 1); |
651 | } |
652 | |
653 | static SplitFragmentData split(const TextFragmentIterator::TextFragment& fragment, float availableWidth, |
654 | const TextFragmentIterator& textFragmentIterator) |
655 | { |
656 | ASSERT(availableWidth >= 0); |
657 | auto left = fragment.start(); |
658 | // Pathological case of (extremely)long string and narrow lines. |
659 | // Adjust the range so that we can pick a reasonable midpoint. |
660 | auto averageCharacterWidth = fragment.width() / fragment.length(); |
661 | auto right = std::min<unsigned>(left + (2 * availableWidth / averageCharacterWidth), fragment.end() - 1); |
662 | // Preserve the left width for the final split position so that we don't need to remeasure the left side again. |
663 | float leftSideWidth = 0; |
664 | while (left < right) { |
665 | auto middle = (left + right) / 2; |
666 | auto width = textFragmentIterator.textWidth(fragment.start(), middle + 1, 0); |
667 | if (width < availableWidth) { |
668 | left = middle + 1; |
669 | leftSideWidth = width; |
670 | } else if (width > availableWidth) |
671 | right = middle; |
672 | else { |
673 | right = middle + 1; |
674 | leftSideWidth = width; |
675 | break; |
676 | } |
677 | } |
678 | return { right, leftSideWidth }; |
679 | } |
680 | |
681 | static TextFragmentIterator::TextFragment splitFragmentToFitLine(TextFragmentIterator::TextFragment& fragmentToSplit, |
682 | const LineState& line, const TextFragmentIterator& textFragmentIterator) |
683 | { |
684 | auto availableWidth = line.availableWidth() - line.width(); |
685 | auto splitFragmentData = split(fragmentToSplit, availableWidth, textFragmentIterator); |
686 | Optional<unsigned> hyphenPosition = WTF::nullopt; |
687 | // Does first character fit this line? |
688 | if (splitFragmentData.position == fragmentToSplit.start()) { |
689 | // Keep at least one character on empty lines. |
690 | if (line.isEmpty()) |
691 | splitFragmentData.width = textFragmentIterator.textWidth(fragmentToSplit.start(), ++splitFragmentData.position, 0); |
692 | } else { |
693 | hyphenPosition = hyphenPositionForFragment(splitFragmentData, fragmentToSplit, line, textFragmentIterator, availableWidth); |
694 | if (hyphenPosition) { |
695 | splitFragmentData.position = *hyphenPosition; |
696 | splitFragmentData.width = textFragmentIterator.textWidth(fragmentToSplit.start(), splitFragmentData.position, 0); |
697 | } |
698 | } |
699 | // If the right side surely does not fit the (next)line, we don't need the width to be kerning/ligature adjusted. |
700 | // Part of it gets re-measured as the left side during next split. |
701 | // This saves measuring long chunk of text repeatedly (see pathological case at ::split). |
702 | auto rightSideWidth = fragmentToSplit.width() - splitFragmentData.width; |
703 | if (rightSideWidth < 2 * availableWidth) |
704 | rightSideWidth = textFragmentIterator.textWidth(splitFragmentData.position, fragmentToSplit.end(), 0); |
705 | return hyphenPosition ? fragmentToSplit.splitWithHyphen(splitFragmentData.position, textFragmentIterator.style().hyphenStringWidth, |
706 | splitFragmentData.width, rightSideWidth) : fragmentToSplit.split(splitFragmentData.position, splitFragmentData.width, rightSideWidth); |
707 | } |
708 | |
709 | enum PreWrapLineBreakRule { Preserve, Ignore }; |
710 | |
711 | static TextFragmentIterator::TextFragment consumeLineBreakIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator, LineState& line, Layout::RunVector& runs, |
712 | PreWrapLineBreakRule preWrapLineBreakRule = PreWrapLineBreakRule::Preserve) |
713 | { |
714 | if (!fragment.isLineBreak()) |
715 | return fragment; |
716 | |
717 | bool isHardLinebreak = fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak; |
718 | // <br> always produces a run. (required by testing output) |
719 | if (isHardLinebreak) |
720 | line.appendFragmentAndCreateRunIfNeeded(fragment, runs); |
721 | |
722 | auto& style = textFragmentIterator.style(); |
723 | if (style.preserveNewline && preWrapLineBreakRule == PreWrapLineBreakRule::Preserve) { |
724 | if (!isHardLinebreak) |
725 | return fragment; |
726 | } |
727 | return textFragmentIterator.nextTextFragment(); |
728 | } |
729 | |
730 | static TextFragmentIterator::TextFragment skipWhitespaceIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator) |
731 | { |
732 | if (!textFragmentIterator.style().collapseWhitespace) |
733 | return fragment; |
734 | |
735 | TextFragmentIterator::TextFragment firstNonWhitespaceFragment = fragment; |
736 | while (firstNonWhitespaceFragment.type() == TextFragmentIterator::TextFragment::Whitespace) |
737 | firstNonWhitespaceFragment = textFragmentIterator.nextTextFragment(); |
738 | return firstNonWhitespaceFragment; |
739 | } |
740 | |
741 | static TextFragmentIterator::TextFragment firstFragment(TextFragmentIterator& textFragmentIterator, LineState& currentLine, const LineState& previousLine, Layout::RunVector& runs) |
742 | { |
743 | // Handle overflow fragment from previous line. |
744 | auto overflowedFragment = previousLine.overflowedFragment(); |
745 | if (overflowedFragment.isEmpty()) |
746 | return skipWhitespaceIfNeeded(textFragmentIterator.nextTextFragment(), textFragmentIterator); |
747 | |
748 | if (overflowedFragment.type() != TextFragmentIterator::TextFragment::Whitespace) |
749 | return overflowedFragment; |
750 | |
751 | // Leading whitespace handling. |
752 | auto& style = textFragmentIterator.style(); |
753 | if (style.breakSpaces) { |
754 | // Leading whitespace created after breaking the previous line. |
755 | // Breaking before the first space after a word is only allowed in combination with break-all or break-word. |
756 | if (style.breakFirstWordOnOverflow || previousLine.hasTrailingWhitespace()) |
757 | return overflowedFragment; |
758 | } |
759 | // Special overflow pre-wrap whitespace handling: skip the overflowed whitespace (even when style says not-collapsible) |
760 | // if we manage to fit at least one character on the previous line. |
761 | auto preWrapIsOn = preWrap(style); |
762 | if ((style.collapseWhitespace || preWrapIsOn) && previousLine.firstCharacterFits()) { |
763 | // If skipping the whitespace puts us on a newline, skip the newline too as we already wrapped the line. |
764 | auto firstFragmentCandidate = consumeLineBreakIfNeeded(textFragmentIterator.nextTextFragment(), textFragmentIterator, currentLine, runs, |
765 | preWrapIsOn ? PreWrapLineBreakRule::Ignore : PreWrapLineBreakRule::Preserve); |
766 | return skipWhitespaceIfNeeded(firstFragmentCandidate, textFragmentIterator); |
767 | } |
768 | return skipWhitespaceIfNeeded(overflowedFragment, textFragmentIterator); |
769 | } |
770 | |
771 | static void forceFragmentToLine(LineState& line, TextFragmentIterator& textFragmentIterator, Layout::RunVector& runs, const TextFragmentIterator::TextFragment& fragment) |
772 | { |
773 | line.appendFragmentAndCreateRunIfNeeded(fragment, runs); |
774 | // Check if there are more fragments to add to the current line. |
775 | auto nextFragment = textFragmentIterator.nextTextFragment(); |
776 | if (fragment.overlapsToNextRenderer()) { |
777 | while (true) { |
778 | if (nextFragment.type() != fragment.type()) |
779 | break; |
780 | line.appendFragmentAndCreateRunIfNeeded(nextFragment, runs); |
781 | // Does it overlap to the next segment? |
782 | if (!nextFragment.overlapsToNextRenderer()) |
783 | return; |
784 | nextFragment = textFragmentIterator.nextTextFragment(); |
785 | } |
786 | } |
787 | // When the forced fragment is followed by either whitespace and/or line break, consume them too, otherwise we end up with an extra whitespace and/or line break. |
788 | nextFragment = skipWhitespaceIfNeeded(nextFragment, textFragmentIterator); |
789 | nextFragment = consumeLineBreakIfNeeded(nextFragment, textFragmentIterator, line, runs); |
790 | line.setOverflowedFragment(nextFragment); |
791 | } |
792 | |
793 | static bool createLineRuns(LineState& line, const LineState& previousLine, Layout::RunVector& runs, TextFragmentIterator& textFragmentIterator) |
794 | { |
795 | const auto& style = textFragmentIterator.style(); |
796 | bool lineCanBeWrapped = style.wrapLines || style.breakFirstWordOnOverflow || style.breakAnyWordOnOverflow; |
797 | auto fragment = firstFragment(textFragmentIterator, line, previousLine, runs); |
798 | while (fragment.type() != TextFragmentIterator::TextFragment::ContentEnd) { |
799 | // Hard and soft linebreaks. |
800 | if (fragment.isLineBreak()) { |
801 | // Add the new line fragment only if there's nothing on the line. (otherwise the extra new line character would show up at the end of the content.) |
802 | if (line.isEmpty() || fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak) { |
803 | if (style.textAlign == TextAlignMode::Right || style.textAlign == TextAlignMode::WebKitRight) |
804 | line.removeTrailingWhitespace(runs); |
805 | line.appendFragmentAndCreateRunIfNeeded(fragment, runs); |
806 | } |
807 | break; |
808 | } |
809 | if (lineCanBeWrapped && !line.fits(fragment.width())) { |
810 | // Overflow wrapping behaviour: |
811 | // 1. Whitesapce collapse on: whitespace is skipped. Jump to next line. |
812 | // 2. Whitespace collapse off: whitespace is wrapped. |
813 | // 3. First, non-whitespace fragment is either wrapped or kept on the line. (depends on overflow-wrap) |
814 | // 5. Non-whitespace fragment when there's already another fragment on the line either gets wrapped (word-break: break-all) |
815 | // or gets pushed to the next line. |
816 | bool emptyLine = line.isEmpty(); |
817 | // Whitespace fragment. |
818 | if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) { |
819 | if (style.collapseWhitespace) { |
820 | // Push collapased whitespace to the next line. |
821 | line.setOverflowedFragment(fragment); |
822 | break; |
823 | } |
824 | if (style.breakSpaces && line.hasWhitespaceFragments() && fragment.length() == 1) { |
825 | // Breaking before the first space after a word is not allowed if there are previous breaking opportunities in the line. |
826 | textFragmentIterator.revertToEndOfFragment(line.revertToLastCompleteFragment(runs)); |
827 | break; |
828 | } |
829 | // Split the whitespace; left part stays on this line, right is pushed to next line. |
830 | line.setOverflowedFragment(splitFragmentToFitLine(fragment, line, textFragmentIterator)); |
831 | line.appendFragmentAndCreateRunIfNeeded(fragment, runs); |
832 | break; |
833 | } |
834 | // Non-whitespace fragment. (!style.wrapLines: bug138102(preserve existing behavior) |
835 | if (((emptyLine && style.breakFirstWordOnOverflow) || style.breakAnyWordOnOverflow) || !style.wrapLines) { |
836 | // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line. |
837 | line.setOverflowedFragment(splitFragmentToFitLine(fragment, line, textFragmentIterator)); |
838 | line.appendFragmentAndCreateRunIfNeeded(fragment, runs); |
839 | break; |
840 | } |
841 | ASSERT(fragment.type() == TextFragmentIterator::TextFragment::NonWhitespace); |
842 | // Find out if this non-whitespace fragment has a hyphen where we can break. |
843 | if (style.shouldHyphenate) { |
844 | auto fragmentToSplit = fragment; |
845 | // Split and check if we actually ended up with a hyphen. |
846 | auto overflowFragment = splitFragmentToFitLine(fragmentToSplit, line, textFragmentIterator); |
847 | if (fragmentToSplit.hasHyphen()) { |
848 | line.setOverflowedFragment(overflowFragment); |
849 | line.appendFragmentAndCreateRunIfNeeded(fragmentToSplit, runs); |
850 | break; |
851 | } |
852 | // No hyphen, no split. |
853 | } |
854 | // Non-breakable non-whitespace first fragment. Add it to the current line. -it overflows though. |
855 | if (emptyLine) { |
856 | forceFragmentToLine(line, textFragmentIterator, runs, fragment); |
857 | break; |
858 | } |
859 | // Non-breakable non-whitespace fragment when there's already content on the line. Push it to the next line. |
860 | ASSERT(line.lastFragment().isValid()); |
861 | if (line.lastFragment().overlapsToNextRenderer()) { |
862 | // Check if this fragment is a continuation of a previous segment. In such cases, we need to remove them all. |
863 | textFragmentIterator.revertToEndOfFragment(line.revertToLastCompleteFragment(runs)); |
864 | break; |
865 | } |
866 | line.setOverflowedFragment(fragment); |
867 | break; |
868 | } |
869 | line.appendFragmentAndCreateRunIfNeeded(fragment, runs); |
870 | // Find the next text fragment. |
871 | fragment = textFragmentIterator.nextTextFragment(line.width()); |
872 | } |
873 | return (fragment.type() == TextFragmentIterator::TextFragment::ContentEnd && line.overflowedFragment().isEmpty()) || line.overflowedFragment().type() == TextFragmentIterator::TextFragment::ContentEnd; |
874 | } |
875 | |
876 | static ExpansionBehavior expansionBehavior(bool isAfterExpansion, bool lastRunOnLine) |
877 | { |
878 | ExpansionBehavior expansionBehavior; |
879 | expansionBehavior = isAfterExpansion ? ForbidLeadingExpansion : AllowLeadingExpansion; |
880 | expansionBehavior |= lastRunOnLine ? ForbidTrailingExpansion : AllowTrailingExpansion; |
881 | return expansionBehavior; |
882 | } |
883 | |
884 | static void justifyRuns(const LineState& line, Layout::RunVector& runs, unsigned firstRunIndex) |
885 | { |
886 | ASSERT(runs.size()); |
887 | auto widthToDistribute = line.availableWidth() - line.width(); |
888 | if (widthToDistribute <= 0) |
889 | return; |
890 | |
891 | auto lastRunIndex = runs.size() - 1; |
892 | ASSERT(firstRunIndex <= lastRunIndex); |
893 | Vector<std::pair<unsigned, ExpansionBehavior>> expansionOpportunityList; |
894 | unsigned expansionOpportunityCountOnThisLine = 0; |
895 | auto isAfterExpansion = true; |
896 | for (auto i = firstRunIndex; i <= lastRunIndex; ++i) { |
897 | const auto& run = runs.at(i); |
898 | unsigned opportunityCountInRun = 0; |
899 | std::tie(opportunityCountInRun, isAfterExpansion) = line.expansionOpportunityCount(run.start, run.end); |
900 | expansionOpportunityList.append(std::make_pair(opportunityCountInRun, expansionBehavior(isAfterExpansion, i == lastRunIndex))); |
901 | expansionOpportunityCountOnThisLine += opportunityCountInRun; |
902 | } |
903 | if (!expansionOpportunityCountOnThisLine) |
904 | return; |
905 | |
906 | ASSERT(expansionOpportunityList.size() == lastRunIndex - firstRunIndex + 1); |
907 | auto expansion = widthToDistribute / expansionOpportunityCountOnThisLine; |
908 | float accumulatedExpansion = 0; |
909 | for (auto i = firstRunIndex; i <= lastRunIndex; ++i) { |
910 | auto& run = runs.at(i); |
911 | unsigned opportunityCountInRun; |
912 | std::tie(opportunityCountInRun, run.expansionBehavior) = expansionOpportunityList.at(i - firstRunIndex); |
913 | run.expansion = opportunityCountInRun * expansion; |
914 | run.logicalLeft += accumulatedExpansion; |
915 | run.logicalRight += (accumulatedExpansion + run.expansion); |
916 | accumulatedExpansion += run.expansion; |
917 | } |
918 | } |
919 | |
920 | static TextAlignMode textAlignForLine(const TextFragmentIterator::Style& style, bool lastLine) |
921 | { |
922 | // Fallback to TextAlignMode::Left (START) alignment for non-collapsable content and for the last line before a forced break or the end of the block. |
923 | auto textAlign = style.textAlign; |
924 | if (textAlign == TextAlignMode::Justify && (!style.collapseWhitespace || lastLine)) |
925 | textAlign = TextAlignMode::Left; |
926 | return textAlign; |
927 | } |
928 | |
929 | static void closeLineEndingAndAdjustRuns(LineState& line, Layout::RunVector& runs, Optional<unsigned> lastRunIndexOfPreviousLine, unsigned& lineCount, |
930 | const TextFragmentIterator& textFragmentIterator, bool lastLineInFlow) |
931 | { |
932 | if (!runs.size() || (lastRunIndexOfPreviousLine && runs.size() - 1 == lastRunIndexOfPreviousLine.value())) |
933 | return; |
934 | removeTrailingWhitespace(line, runs, textFragmentIterator); |
935 | if (!runs.size()) |
936 | return; |
937 | // Adjust runs' position by taking line's alignment into account. |
938 | const auto& style = textFragmentIterator.style(); |
939 | auto firstRunIndex = lastRunIndexOfPreviousLine ? lastRunIndexOfPreviousLine.value() + 1 : 0; |
940 | auto lineLogicalLeft = line.logicalLeftOffset(); |
941 | auto textAlign = textAlignForLine(style, lastLineInFlow || (line.lastFragment().isValid() && line.lastFragment().type() == TextFragmentIterator::TextFragment::HardLineBreak)); |
942 | if (textAlign == TextAlignMode::Justify) |
943 | justifyRuns(line, runs, firstRunIndex); |
944 | else |
945 | lineLogicalLeft = computeLineLeft(textAlign, line.availableWidth(), line.width(), line.logicalLeftOffset()); |
946 | for (auto i = firstRunIndex; i < runs.size(); ++i) { |
947 | runs[i].logicalLeft += lineLogicalLeft; |
948 | runs[i].logicalRight += lineLogicalLeft; |
949 | } |
950 | runs.last().isEndOfLine = true; |
951 | ++lineCount; |
952 | } |
953 | |
954 | static void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsigned& lineCount) |
955 | { |
956 | LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore(); |
957 | LayoutUnit lineHeight = lineHeightFromFlow(flow); |
958 | LineState line; |
959 | unsigned numberOfPrecedingLinesWithHyphen = 0; |
960 | bool isEndOfContent = false; |
961 | TextFragmentIterator textFragmentIterator = TextFragmentIterator(flow); |
962 | Optional<unsigned> lastRunIndexOfPreviousLine; |
963 | do { |
964 | flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore); |
965 | LineState previousLine = line; |
966 | line = LineState(); |
967 | updateLineConstrains(flow, line, previousLine, numberOfPrecedingLinesWithHyphen, textFragmentIterator.style(), !lineCount); |
968 | isEndOfContent = createLineRuns(line, previousLine, runs, textFragmentIterator); |
969 | closeLineEndingAndAdjustRuns(line, runs, lastRunIndexOfPreviousLine, lineCount, textFragmentIterator, isEndOfContent); |
970 | if (runs.size()) |
971 | lastRunIndexOfPreviousLine = runs.size() - 1; |
972 | } while (!isEndOfContent); |
973 | } |
974 | |
975 | std::unique_ptr<Layout> create(RenderBlockFlow& flow) |
976 | { |
977 | unsigned lineCount = 0; |
978 | Layout::RunVector runs; |
979 | createTextRuns(runs, flow, lineCount); |
980 | return Layout::create(runs, lineCount, flow); |
981 | } |
982 | |
983 | std::unique_ptr<Layout> Layout::create(const RunVector& runVector, unsigned lineCount, const RenderBlockFlow& blockFlow) |
984 | { |
985 | void* slot = WTF::fastMalloc(sizeof(Layout) + sizeof(Run) * runVector.size()); |
986 | return std::unique_ptr<Layout>(new (NotNull, slot) Layout(runVector, lineCount, blockFlow)); |
987 | } |
988 | |
989 | Layout::Layout(const RunVector& runVector, unsigned lineCount, const RenderBlockFlow& blockFlow) |
990 | : m_lineCount(lineCount) |
991 | , m_runCount(runVector.size()) |
992 | , m_blockFlowRenderer(blockFlow) |
993 | { |
994 | memcpy(m_runs, runVector.data(), m_runCount * sizeof(Run)); |
995 | } |
996 | |
997 | const RunResolver& Layout::runResolver() const |
998 | { |
999 | if (!m_runResolver) |
1000 | m_runResolver = std::make_unique<RunResolver>(m_blockFlowRenderer, *this); |
1001 | return *m_runResolver; |
1002 | } |
1003 | |
1004 | Layout::~Layout() |
1005 | { |
1006 | simpleLineLayoutWillBeDeleted(*this); |
1007 | } |
1008 | |
1009 | } |
1010 | } |
1011 | |