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
60namespace WebCore {
61namespace 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
78template <typename CharacterType> AvoidanceReasonFlags canUseForCharacter(CharacterType, bool textIsJustified, IncludeReasons);
79
80template<> 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
104template<> AvoidanceReasonFlags canUseForCharacter(LChar, bool, IncludeReasons)
105{
106 return { };
107}
108
109template <typename CharacterType>
110static 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
149static 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
156static 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
199static 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
251AvoidanceReasonFlags 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
353bool canUseFor(const RenderBlockFlow& flow)
354{
355 return canUseForWithReason(flow, IncludeReasons::First) == NoReason;
356}
357
358static 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
382static void revertAllRunsOnCurrentLine(Layout::RunVector& runs)
383{
384 while (!runs.isEmpty() && !runs.last().isEndOfLine)
385 runs.removeLast();
386}
387
388static 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
408class LineState {
409public:
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 extra) 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
559private:
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
584static bool preWrap(const TextFragmentIterator::Style& style)
585{
586 return style.wrapLines && !style.collapseWhitespace;
587}
588
589static 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
604static 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
620struct SplitFragmentData {
621 unsigned position;
622 float width;
623};
624static 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
653static 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
681static 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
709enum PreWrapLineBreakRule { Preserve, Ignore };
710
711static 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
730static 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
741static 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
771static 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
793static 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
876static 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
884static 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
920static 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
929static 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
954static 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
975std::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
983std::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
989Layout::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
997const 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
1004Layout::~Layout()
1005{
1006 simpleLineLayoutWillBeDeleted(*this);
1007}
1008
1009}
1010}
1011