blob: e28eab3ac502b18560718786c1f79d4f6f467b75 [file] [log] [blame]
antti@apple.com940f5872013-10-24 20:31:11 +00001/*
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
mmaxfield@apple.come4058ae2018-11-06 23:06:37 +000029#include "DocumentMarkerController.h"
antti@apple.com940f5872013-10-24 20:31:11 +000030#include "FontCache.h"
antti@apple.com2ec05012013-10-30 17:40:06 +000031#include "Frame.h"
antti@apple.com940f5872013-10-24 20:31:11 +000032#include "GraphicsContext.h"
antti@apple.com02a025d2013-11-13 13:25:32 +000033#include "HTMLTextFormControlElement.h"
antti@apple.com940f5872013-10-24 20:31:11 +000034#include "HitTestLocation.h"
35#include "HitTestRequest.h"
36#include "HitTestResult.h"
zalan@apple.com89b951f2017-01-24 21:38:20 +000037#include "Hyphenation.h"
antti@apple.com940f5872013-10-24 20:31:11 +000038#include "InlineTextBox.h"
39#include "LineWidth.h"
zalan@apple.com58cb2182015-12-03 21:59:17 +000040#include "Logging.h"
antti@apple.com940f5872013-10-24 20:31:11 +000041#include "PaintInfo.h"
42#include "RenderBlockFlow.h"
zalan@apple.com29a2eb62014-11-20 18:11:07 +000043#include "RenderChildIterator.h"
hyatt@apple.com4e0bf862017-09-27 20:54:17 +000044#include "RenderFragmentedFlow.h"
zalan@apple.com8bf2a912015-04-10 03:15:50 +000045#include "RenderLineBreak.h"
hyatt@apple.com4e0bf862017-09-27 20:54:17 +000046#include "RenderMultiColumnFlow.h"
antti@apple.com940f5872013-10-24 20:31:11 +000047#include "RenderStyle.h"
48#include "RenderText.h"
antti@apple.com02a025d2013-11-13 13:25:32 +000049#include "RenderTextControl.h"
zalan@apple.com06e2c402017-03-09 15:25:12 +000050#include "RenderView.h"
antti@apple.com2ec05012013-10-30 17:40:06 +000051#include "Settings.h"
zalan@apple.comf3941b22014-11-17 23:17:51 +000052#include "SimpleLineLayoutFlowContents.h"
antti@apple.com01a04122013-11-20 21:23:19 +000053#include "SimpleLineLayoutFunctions.h"
zalan@apple.com6cda0fa2018-05-09 00:20:18 +000054#include "SimpleLineLayoutResolver.h"
zalan@apple.com1802def2015-02-03 03:08:33 +000055#include "SimpleLineLayoutTextFragmentIterator.h"
antti@apple.com940f5872013-10-24 20:31:11 +000056#include "Text.h"
57#include "TextPaintStyle.h"
mmaxfield@apple.com2cc9b8d2017-10-11 20:30:16 +000058#include <pal/Logging.h>
antti@apple.com940f5872013-10-24 20:31:11 +000059
60namespace WebCore {
61namespace SimpleLineLayout {
62
zalan@apple.com58cb2182015-12-03 21:59:17 +000063#ifndef NDEBUG
zalan@apple.comc1c97602015-12-07 04:40:35 +000064#define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \
zalan@apple.com58cb2182015-12-03 21:59:17 +000065 reasons |= reason; \
zalan@apple.comc1c97602015-12-07 04:40:35 +000066 if (includeReasons == IncludeReasons::First) \
zalan@apple.com58cb2182015-12-03 21:59:17 +000067 return reasons; \
68 }
69#else
zalan@apple.comc1c97602015-12-07 04:40:35 +000070#define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \
71 ASSERT_UNUSED(includeReasons, includeReasons == IncludeReasons::First); \
zalan@apple.com58cb2182015-12-03 21:59:17 +000072 reasons |= reason; \
73 return reasons; \
74 }
75#endif
76
zalan@apple.com1ecaa802017-02-04 16:34:57 +000077
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
zalan@apple.com9e7bf6e2017-02-05 14:41:10 +000091 if (U16_IS_SURROGATE(character))
92 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSurrogatePair, reasons, includeReasons);
93
zalan@apple.com1ecaa802017-02-04 16:34:57 +000094 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
antti@apple.comaa930a62013-11-08 12:53:19 +0000109template <typename CharacterType>
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000110static AvoidanceReasonFlags canUseForText(const CharacterType* text, unsigned length, const FontCascade& fontCascade, Optional<float> lineHeightConstraint,
zalan@apple.com99603d12017-01-27 19:24:26 +0000111 bool textIsJustified, IncludeReasons includeReasons)
antti@apple.com940f5872013-10-24 20:31:11 +0000112{
zalan@apple.comc1c97602015-12-07 04:40:35 +0000113 AvoidanceReasonFlags reasons = { };
zalan@apple.com0508f0f2017-02-04 01:42:12 +0000114 auto& primaryFont = fontCascade.primaryFont();
zalan@apple.com2c506312017-04-29 21:16:44 +0000115 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
antti@apple.comaa930a62013-11-08 12:53:19 +0000124 for (unsigned i = 0; i < length; ++i) {
zalan@apple.com1ecaa802017-02-04 16:34:57 +0000125 auto character = text[i];
126 if (FontCascade::treatAsSpace(character))
antti@apple.comaa930a62013-11-08 12:53:19 +0000127 continue;
128
antti@apple.comaa930a62013-11-08 12:53:19 +0000129 if (character == softHyphen)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000130 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSoftHyphen, reasons, includeReasons);
antti@apple.comaa930a62013-11-08 12:53:19 +0000131
zalan@apple.com9e7bf6e2017-02-05 14:41:10 +0000132 auto characterReasons = canUseForCharacter(character, textIsJustified, includeReasons);
133 if (characterReasons != NoReason)
134 SET_REASON_AND_RETURN_IF_NEEDED(characterReasons, reasons, includeReasons);
135
zalan@apple.com0508f0f2017-02-04 01:42:12 +0000136 auto glyphData = fontCascade.glyphDataForCharacter(character, false);
137 if (!glyphData.isValid() || glyphData.font != &primaryFont)
138 SET_REASON_AND_RETURN_IF_NEEDED(FlowPrimaryFontIsInsufficient, reasons, includeReasons);
139
zalan@apple.com2c506312017-04-29 21:16:44 +0000140 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 }
antti@apple.comaa930a62013-11-08 12:53:19 +0000145 }
zalan@apple.com58cb2182015-12-03 21:59:17 +0000146 return reasons;
antti@apple.comaa930a62013-11-08 12:53:19 +0000147}
commit-queue@webkit.org89097862015-07-14 21:19:50 +0000148
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000149static AvoidanceReasonFlags canUseForText(StringView text, const FontCascade& fontCascade, Optional<float> lineHeightConstraint, bool textIsJustified, IncludeReasons includeReasons)
antti@apple.comaa930a62013-11-08 12:53:19 +0000150{
zalan@apple.com1ecaa802017-02-04 16:34:57 +0000151 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);
antti@apple.com940f5872013-10-24 20:31:11 +0000154}
commit-queue@webkit.org89097862015-07-14 21:19:50 +0000155
zalan@apple.comc1c97602015-12-07 04:40:35 +0000156static AvoidanceReasonFlags canUseForFontAndText(const RenderBlockFlow& flow, IncludeReasons includeReasons)
antti@apple.com940f5872013-10-24 20:31:11 +0000157{
zalan@apple.comc1c97602015-12-07 04:40:35 +0000158 AvoidanceReasonFlags reasons = { };
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000159 // We assume that all lines have metrics based purely on the primary font.
160 const auto& style = flow.style();
zalan@apple.com0508f0f2017-02-04 01:42:12 +0000161 auto& fontCascade = style.fontCascade();
mmaxfield@apple.com0bc95662017-05-16 01:37:43 +0000162 if (fontCascade.primaryFont().isInterstitial())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000163 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsMissingPrimaryFont, reasons, includeReasons);
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000164 Optional<float> lineHeightConstraint;
simon.fraser@apple.com815adf52019-10-13 16:02:54 +0000165 if (style.lineBoxContain().contains(LineBoxContain::Glyphs))
zalan@apple.com99603d12017-01-27 19:24:26 +0000166 lineHeightConstraint = lineHeightFromFlow(flow).toFloat();
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000167 bool flowIsJustified = style.textAlign() == TextAlignMode::Justify;
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000168 for (const auto& textRenderer : childrenOfType<RenderText>(flow)) {
zalan@apple.com1ecaa802017-02-04 16:34:57 +0000169 // FIXME: Do not return until after checking all children.
darin@apple.com5917cb12017-11-23 17:32:42 +0000170 if (textRenderer.text().isEmpty())
zalan@apple.com1ecaa802017-02-04 16:34:57 +0000171 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsEmpty, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000172 if (textRenderer.isCombineText())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000173 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsCombineText, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000174 if (textRenderer.isCounter())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000175 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderCounter, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000176 if (textRenderer.isQuote())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000177 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderQuote, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000178 if (textRenderer.isTextFragment())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000179 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsTextFragment, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000180 if (textRenderer.isSVGInlineText())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000181 SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsSVGInlineText, reasons, includeReasons);
zalan@apple.com3eb0dd72017-02-05 18:01:04 +0000182 if (!textRenderer.canUseSimpleFontCodePath()) {
183 // No need to check the code path at this point. We already know it can't be simple.
zalan@apple.comdcc97ad32017-02-04 06:03:29 +0000184 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasComplexFontCodePath, reasons, includeReasons);
zalan@apple.com3eb0dd72017-02-05 18:01:04 +0000185 } else {
commit-queue@webkit.orgb6be03c2018-04-09 23:02:29 +0000186 TextRun run(String(textRenderer.text()));
zalan@apple.com3eb0dd72017-02-05 18:01:04 +0000187 run.setCharacterScanForCodePath(false);
188 if (style.fontCascade().codePath(run) != FontCascade::Simple)
189 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasComplexFontCodePath, reasons, includeReasons);
190 }
zalan@apple.com58cb2182015-12-03 21:59:17 +0000191
zalan@apple.com1ecaa802017-02-04 16:34:57 +0000192 auto textReasons = canUseForText(textRenderer.stringView(), fontCascade, lineHeightConstraint, flowIsJustified, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000193 if (textReasons != NoReason)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000194 SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons);
zalan@apple.com29a2eb62014-11-20 18:11:07 +0000195 }
zalan@apple.com58cb2182015-12-03 21:59:17 +0000196 return reasons;
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000197}
198
zalan@apple.comc1c97602015-12-07 04:40:35 +0000199static AvoidanceReasonFlags canUseForStyle(const RenderStyle& style, IncludeReasons includeReasons)
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000200{
zalan@apple.comc1c97602015-12-07 04:40:35 +0000201 AvoidanceReasonFlags reasons = { };
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000202 if (style.textOverflow() == TextOverflow::Ellipsis)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000203 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons);
zalan@apple.comde4cd162019-03-28 16:02:24 +0000204 if (style.textUnderlinePosition() != TextUnderlinePosition::Auto || !style.textUnderlineOffset().isAuto() || !style.textDecorationThickness().isAuto())
zalan@apple.com114e8aa2016-01-02 19:42:22 +0000205 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedUnderlineDecoration, reasons, includeReasons);
antti@apple.com940f5872013-10-24 20:31:11 +0000206 // Non-visible overflow should be pretty easy to support.
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000207 if (style.overflowX() != Overflow::Visible || style.overflowY() != Overflow::Visible)
zalan@apple.com7eff9dd2017-02-08 18:33:46 +0000208 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOverflowNotVisible, reasons, includeReasons);
antti@apple.com940f5872013-10-24 20:31:11 +0000209 if (!style.isLeftToRightDirection())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000210 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotLTR, reasons, includeReasons);
simon.fraser@apple.com815adf52019-10-13 16:02:54 +0000211 if (!(style.lineBoxContain().contains(LineBoxContain::Block)))
zalan@apple.comc1c97602015-12-07 04:40:35 +0000212 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBoxContainProperty, reasons, includeReasons);
wenson_hsieh@apple.com5773fcd2020-09-07 02:22:02 +0000213 if (style.writingMode() != WritingMode::TopToBottom)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000214 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotTopToBottom, reasons, includeReasons);
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000215 if (style.lineBreak() != LineBreak::Auto)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000216 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBreak, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000217 if (style.unicodeBidi() != UBNormal)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000218 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonNormalUnicodeBiDi, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000219 if (style.rtlOrdering() != Order::Logical)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000220 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasRTLOrdering, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000221 if (style.lineAlign() != LineAlign::None)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000222 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineAlignEdges, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000223 if (style.lineSnap() != LineSnap::None)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000224 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineSnap, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000225 if (style.textEmphasisFill() != TextEmphasisFill::Filled || style.textEmphasisMark() != TextEmphasisMark::None)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000226 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextEmphasisFillOrMark, reasons, includeReasons);
antti@apple.com940f5872013-10-24 20:31:11 +0000227 if (style.textShadow())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000228 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextShadow, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000229 if (style.hasPseudoStyle(PseudoId::FirstLine))
zalan@apple.comc1c97602015-12-07 04:40:35 +0000230 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000231 if (style.hasPseudoStyle(PseudoId::FirstLetter))
zalan@apple.comc1c97602015-12-07 04:40:35 +0000232 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLetter, reasons, includeReasons);
antti@apple.com940f5872013-10-24 20:31:11 +0000233 if (style.hasTextCombine())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000234 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextCombine, reasons, includeReasons);
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000235 if (style.backgroundClip() == FillBox::Text)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000236 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextFillBox, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000237 if (style.borderFit() == BorderFit::Lines)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000238 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasBorderFitLines, reasons, includeReasons);
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000239 if (style.lineBreak() != LineBreak::Auto)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000240 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonAutoLineBreak, reasons, includeReasons);
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000241 if (style.nbspMode() != NBSPMode::Normal)
zalan@apple.com7c51fae2017-01-20 22:12:02 +0000242 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasWebKitNBSPMode, reasons, includeReasons);
mmaxfield@apple.com608dd562020-04-16 05:57:41 +0000243 // Special handling of text-security:disc is not yet implemented in the simple line layout code path.
244 // See RenderBlock::updateSecurityDiscCharacters.
245 if (style.textSecurity() != TextSecurity::None)
246 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextSecurity, reasons, includeReasons);
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000247 if (style.hyphens() == Hyphens::Auto) {
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000248 auto textReasons = canUseForText(style.hyphenString(), style.fontCascade(), WTF::nullopt, false, includeReasons);
zalan@apple.comd075ea22017-05-08 18:19:31 +0000249 if (textReasons != NoReason)
250 SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons);
251 }
zalan@apple.com58cb2182015-12-03 21:59:17 +0000252 return reasons;
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000253}
zalan@apple.com62bd3d22014-11-25 00:10:17 +0000254
zalan@apple.com79dc43f2017-02-26 07:19:22 +0000255AvoidanceReasonFlags canUseForWithReason(const RenderBlockFlow& flow, IncludeReasons includeReasons)
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000256{
zalan@apple.com58cb2182015-12-03 21:59:17 +0000257#ifndef NDEBUG
258 static std::once_flag onceFlag;
259 std::call_once(onceFlag, [] {
mmaxfield@apple.com2cc9b8d2017-10-11 20:30:16 +0000260 PAL::registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutCoverage", WTF::Function<void()> { printSimpleLineLayoutCoverage });
261 PAL::registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutReasons", WTF::Function<void()> { printSimpleLineLayoutBlockList });
262 PAL::registerNotifyCallback("com.apple.WebKit.toggleSimpleLineLayout", WTF::Function<void()> { toggleSimpleLineLayout });
zalan@apple.com58cb2182015-12-03 21:59:17 +0000263 });
264#endif
zalan@apple.comc1c97602015-12-07 04:40:35 +0000265 AvoidanceReasonFlags reasons = { };
zalan@apple.coma1cff3a2017-01-14 18:45:20 +0000266 if (!flow.settings().simpleLineLayoutEnabled())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000267 SET_REASON_AND_RETURN_IF_NEEDED(FeatureIsDisabled, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000268 if (!flow.parent())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000269 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoParent, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000270 if (!flow.firstChild())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000271 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoChild, reasons, includeReasons);
hyatt@apple.com4e0bf862017-09-27 20:54:17 +0000272 if (flow.fragmentedFlowState() != RenderObject::NotInsideFragmentedFlow) {
273 auto* fragmentedFlow = flow.enclosingFragmentedFlow();
274 if (!is<RenderMultiColumnFlow>(fragmentedFlow))
zalan@apple.com06e2c402017-03-09 15:25:12 +0000275 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsInsideANonMultiColumnThread, reasons, includeReasons);
hyatt@apple.com4e0bf862017-09-27 20:54:17 +0000276 auto& columnThread = downcast<RenderMultiColumnFlow>(*fragmentedFlow);
zalan@apple.com06e2c402017-03-09 15:25:12 +0000277 if (columnThread.parent() != &flow.view())
278 SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowIsNotTopLevel, reasons, includeReasons);
279 if (columnThread.hasColumnSpanner())
280 SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowHasColumnSpanner, reasons, includeReasons);
281 auto& style = flow.style();
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000282 if (style.verticalAlign() != VerticalAlign::Baseline)
zalan@apple.com06e2c402017-03-09 15:25:12 +0000283 SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowVerticalAlign, reasons, includeReasons);
284 if (style.isFloating())
285 SET_REASON_AND_RETURN_IF_NEEDED(MultiColumnFlowIsFloating, reasons, includeReasons);
286 }
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000287 if (!flow.isHorizontalWritingMode())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000288 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHorizonalWritingMode, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000289 if (flow.hasOutline())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000290 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOutline, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000291 if (flow.isRubyText() || flow.isRubyBase())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000292 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsRuby, reasons, includeReasons);
commit-queue@webkit.org02cb0002018-05-28 00:49:21 +0000293 if (!flow.style().hangingPunctuation().isEmpty())
hyatt@apple.com41c12c92016-03-02 22:29:26 +0000294 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHangingPunctuation, reasons, includeReasons);
295
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000296 // Printing does pagination without a flow thread.
297 if (flow.document().paginated())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000298 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsPaginated, reasons, includeReasons);
zalan@apple.comc1e7f802015-12-31 19:27:58 +0000299 if (flow.firstLineBlock())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000300 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons);
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000301 if (flow.isAnonymousBlock() && flow.parent()->style().textOverflow() == TextOverflow::Ellipsis)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000302 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000303 if (flow.parent()->isDeprecatedFlexibleBox())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000304 SET_REASON_AND_RETURN_IF_NEEDED(FlowIsDepricatedFlexBox, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000305 // FIXME: Placeholders do something strange.
306 if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000307 SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsPlaceholderElement, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000308 // FIXME: Implementation of wrap=hard looks into lineboxes.
rniwa@webkit.orgbda54a02016-07-18 00:39:37 +0000309 if (flow.parent()->isTextArea() && flow.parent()->element()->hasAttributeWithoutSynchronization(HTMLNames::wrapAttr))
zalan@apple.comc1c97602015-12-07 04:40:35 +0000310 SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsTextAreaWithWrapping, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000311 // This currently covers <blockflow>#text</blockflow>, <blockflow>#text<br></blockflow> and mutiple (sibling) RenderText cases.
312 // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover.
313 for (const auto* child = flow.firstChild(); child;) {
megan_gardner@apple.comc66f2b82020-02-10 19:32:19 +0000314 if (child->selectionState() != RenderObject::HighlightState::None)
zalan@apple.comf433a512015-12-11 07:31:28 +0000315 SET_REASON_AND_RETURN_IF_NEEDED(FlowChildIsSelected, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000316 if (is<RenderText>(*child)) {
mmaxfield@apple.come4058ae2018-11-06 23:06:37 +0000317 const auto& renderText = downcast<RenderText>(*child);
zalan@apple.com095cb602018-12-17 19:42:55 +0000318 if (renderText.textNode() && !renderText.document().markers().markersFor(*renderText.textNode()).isEmpty())
mmaxfield@apple.come4058ae2018-11-06 23:06:37 +0000319 SET_REASON_AND_RETURN_IF_NEEDED(FlowIncludesDocumentMarkers, reasons, includeReasons);
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000320 child = child->nextSibling();
321 continue;
322 }
commit-queue@webkit.org1a4e6672018-05-21 16:55:45 +0000323 if (is<RenderLineBreak>(child) && !downcast<RenderLineBreak>(*child).isWBR() && child->style().clear() == Clear::None) {
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000324 child = child->nextSibling();
325 continue;
326 }
zalan@apple.comc1c97602015-12-07 04:40:35 +0000327 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonSupportedChild, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000328 break;
zalan@apple.com1cf32e62015-11-17 21:16:07 +0000329 }
zalan@apple.comc1c97602015-12-07 04:40:35 +0000330 auto styleReasons = canUseForStyle(flow.style(), includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000331 if (styleReasons != NoReason)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000332 SET_REASON_AND_RETURN_IF_NEEDED(styleReasons, reasons, includeReasons);
zalan@apple.com62bd3d22014-11-25 00:10:17 +0000333 // 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.
antti@apple.com01a04122013-11-20 21:23:19 +0000334 if (flow.containsFloats()) {
zalan@apple.com62bd3d22014-11-25 00:10:17 +0000335 float minimumWidthNeeded = std::numeric_limits<float>::max();
zalan@apple.com57c7fc72014-11-25 03:47:57 +0000336 for (const auto& textRenderer : childrenOfType<RenderText>(flow)) {
zalan@apple.com62bd3d22014-11-25 00:10:17 +0000337 minimumWidthNeeded = std::min(minimumWidthNeeded, textRenderer.minLogicalWidth());
338
bjonesbe@adobe.com1be249b2015-01-09 21:01:53 +0000339 for (auto& floatingObject : *flow.floatingObjectSet()) {
340 ASSERT(floatingObject);
bjonesbe@adobe.com1be249b2015-01-09 21:01:53 +0000341 // if a float has a shape, we cannot tell if content will need to be shifted until after we lay it out,
342 // since the amount of space is not uniform for the height of the float.
343 if (floatingObject->renderer().shapeOutsideInfo())
zalan@apple.comc1c97602015-12-07 04:40:35 +0000344 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons);
zalan@apple.com64761fe2016-03-02 21:42:22 +0000345 float availableWidth = flow.availableLogicalWidthForLine(floatingObject->y(), DoNotIndentText);
zalan@apple.com57c7fc72014-11-25 03:47:57 +0000346 if (availableWidth < minimumWidthNeeded)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000347 SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons);
zalan@apple.com57c7fc72014-11-25 03:47:57 +0000348 }
antti@apple.combe9d3e12014-05-11 09:42:47 +0000349 }
antti@apple.com01a04122013-11-20 21:23:19 +0000350 }
zalan@apple.comc1c97602015-12-07 04:40:35 +0000351 auto fontAndTextReasons = canUseForFontAndText(flow, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000352 if (fontAndTextReasons != NoReason)
zalan@apple.comc1c97602015-12-07 04:40:35 +0000353 SET_REASON_AND_RETURN_IF_NEEDED(fontAndTextReasons, reasons, includeReasons);
zalan@apple.com58cb2182015-12-03 21:59:17 +0000354 return reasons;
355}
356
357bool canUseFor(const RenderBlockFlow& flow)
358{
zalan@apple.comc1c97602015-12-07 04:40:35 +0000359 return canUseForWithReason(flow, IncludeReasons::First) == NoReason;
antti@apple.com940f5872013-10-24 20:31:11 +0000360}
361
zalan@apple.comfcfec3b2020-08-28 20:21:39 +0000362bool canUseForAfterStyleChange(const RenderBlockFlow& blockContainer, StyleDifference diff)
363{
364 switch (diff) {
365 case StyleDifference::Equal:
366 case StyleDifference::RecompositeLayer:
367 return true;
368 case StyleDifference::Repaint:
369 case StyleDifference::RepaintIfTextOrBorderOrOutline:
370 case StyleDifference::RepaintLayer:
371 // FIXME: We could do a more focused style check by matching RendererStyle::changeRequiresRepaint&co.
372 return canUseForStyle(blockContainer.style(), IncludeReasons::First) == NoReason;
373 case StyleDifference::LayoutPositionedMovementOnly:
374 return true;
375 case StyleDifference::SimplifiedLayout:
376 case StyleDifference::SimplifiedLayoutAndPositionedMovement:
377 return canUseForStyle(blockContainer.style(), IncludeReasons::First) == NoReason;
378 case StyleDifference::Layout:
379 case StyleDifference::NewStyle:
380 return canUseFor(blockContainer);
381 }
382 ASSERT_NOT_REACHED();
383 return canUseFor(blockContainer);
384}
385
zalan@apple.com539173e2017-03-07 21:02:15 +0000386static void revertAllRunsOnCurrentLine(Layout::RunVector& runs)
387{
388 while (!runs.isEmpty() && !runs.last().isEndOfLine)
389 runs.removeLast();
390}
391
zalan@apple.come3e62602017-02-03 21:17:28 +0000392static void revertRuns(Layout::RunVector& runs, unsigned positionToRevertTo, float width)
zalan@apple.com99ff6302015-03-18 14:48:01 +0000393{
zalan@apple.come3e62602017-02-03 21:17:28 +0000394 while (runs.size()) {
395 auto& lastRun = runs.last();
396 if (lastRun.end <= positionToRevertTo)
397 break;
398 if (lastRun.start >= positionToRevertTo) {
399 // Revert this run completely.
400 width -= (lastRun.logicalRight - lastRun.logicalLeft);
401 runs.removeLast();
402 } else {
zalan@apple.com99ff6302015-03-18 14:48:01 +0000403 lastRun.logicalRight -= width;
zalan@apple.come3e62602017-02-03 21:17:28 +0000404 width = 0;
405 lastRun.end = positionToRevertTo;
406 // Partial removal.
zalan@apple.com99ff6302015-03-18 14:48:01 +0000407 break;
408 }
zalan@apple.com99ff6302015-03-18 14:48:01 +0000409 }
410}
411
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000412class LineState {
413public:
414 void setAvailableWidth(float width) { m_availableWidth = width; }
zalan@apple.com99ff6302015-03-18 14:48:01 +0000415 void setCollapedWhitespaceWidth(float width) { m_collapsedWhitespaceWidth = width; }
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000416 void setLogicalLeftOffset(float offset) { m_logicalLeftOffset = offset; }
zalan@apple.com1802def2015-02-03 03:08:33 +0000417 void setOverflowedFragment(const TextFragmentIterator::TextFragment& fragment) { m_overflowedFragment = fragment; }
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000418 void setNeedsAllFragments()
419 {
420 ASSERT(!m_fragments);
421 m_fragments.emplace();
422 }
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000423 void setHyphenationDisabled() { m_hyphenationDisabled = true; }
424 bool isHyphenationDisabled() const { return m_hyphenationDisabled; }
zalan@apple.com6ab19072014-10-28 17:57:14 +0000425
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000426 float availableWidth() const { return m_availableWidth; }
427 float logicalLeftOffset() const { return m_logicalLeftOffset; }
zalan@apple.com1802def2015-02-03 03:08:33 +0000428 const TextFragmentIterator::TextFragment& overflowedFragment() const { return m_overflowedFragment; }
jfernandez@igalia.com2c9d1252019-04-08 20:31:05 +0000429 bool hasTrailingWhitespace() const { return m_lastFragment.type() == TextFragmentIterator::TextFragment::Whitespace && m_lastFragment.length() > 0; }
430 bool hasWhitespaceFragments() const { return m_lastWhitespaceFragment != WTF::nullopt; }
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000431 TextFragmentIterator::TextFragment lastFragment() const { return m_lastFragment; }
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000432 bool isWhitespaceOnly() const { return m_trailingWhitespaceWidth && m_runsWidth == m_trailingWhitespaceWidth; }
433 bool fits(float extra) const { return m_availableWidth >= m_runsWidth + extra; }
zalan@apple.comc23decf2015-01-28 23:36:46 +0000434 bool firstCharacterFits() const { return m_firstCharacterFits; }
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000435 float width() const { return m_runsWidth; }
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000436 std::pair<unsigned, bool> expansionOpportunityCount(unsigned from, unsigned to) const
437 {
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000438 ASSERT(m_fragments);
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000439 // linebreak runs are special.
440 if (from == to)
441 return std::make_pair(0, false);
442 unsigned expansionOpportunityCount = 0;
443 auto previousFragmentType = TextFragmentIterator::TextFragment::ContentEnd;
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000444 for (const auto& fragment : *m_fragments) {
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000445 if (fragment.end() <= from)
446 continue;
447 auto currentFragmentType = fragment.type();
448 auto expansionOpportunity = this->expansionOpportunity(currentFragmentType, previousFragmentType);
449 if (expansionOpportunity)
450 ++expansionOpportunityCount;
451 previousFragmentType = currentFragmentType;
452 if (fragment.end() >= to)
453 return std::make_pair(expansionOpportunityCount, expansionOpportunity);
454 }
455 ASSERT_NOT_REACHED();
456 return std::make_pair(expansionOpportunityCount, false);
457 }
458
zalan@apple.com99ff6302015-03-18 14:48:01 +0000459 bool isEmpty() const
460 {
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000461 if (!m_lastFragment.isValid())
zalan@apple.com99ff6302015-03-18 14:48:01 +0000462 return true;
463 if (!m_lastCompleteFragment.isEmpty())
464 return false;
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000465 return m_lastFragment.overlapsToNextRenderer();
466 }
467
468 static inline unsigned endPositionForCollapsedFragment(const TextFragmentIterator::TextFragment& fragment)
469 {
470 return fragment.isCollapsed() ? fragment.start() + 1 : fragment.end();
zalan@apple.com99ff6302015-03-18 14:48:01 +0000471 }
zalan@apple.comc23decf2015-01-28 23:36:46 +0000472
zalan@apple.com99ff6302015-03-18 14:48:01 +0000473 void appendFragmentAndCreateRunIfNeeded(const TextFragmentIterator::TextFragment& fragment, Layout::RunVector& runs)
zalan@apple.com6ab19072014-10-28 17:57:14 +0000474 {
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000475 // Adjust end position while collapsing.
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000476 unsigned endPosition = endPositionForCollapsedFragment(fragment);
zalan@apple.com99ff6302015-03-18 14:48:01 +0000477 // New line needs new run.
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000478 if (!m_runsWidth) {
479 ASSERT(!m_uncompletedWidth);
antti@apple.comb8db0352019-09-26 18:25:47 +0000480 runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false, fragment.hasHyphen(), fragment.isLineBreak()));
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000481 } else {
zalan@apple.com99ff6302015-03-18 14:48:01 +0000482 // Advance last completed fragment when the previous fragment is all set (including multiple parts across renderers)
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000483 if ((m_lastFragment.type() != fragment.type()) || !m_lastFragment.overlapsToNextRenderer()) {
484 m_lastCompleteFragment = m_lastFragment;
485 m_uncompletedWidth = fragment.width();
486 } else
487 m_uncompletedWidth += fragment.width();
zalan@apple.com99ff6302015-03-18 14:48:01 +0000488 // Collapse neighbouring whitespace, if they are across multiple renderers and are not collapsed yet.
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000489 if (m_lastFragment.isCollapsible() && fragment.isCollapsible()) {
490 ASSERT(m_lastFragment.isLastInRenderer());
491 if (!m_lastFragment.isCollapsed()) {
492 // Line width needs to be adjusted so that now it takes collapsing into consideration.
493 m_runsWidth -= (m_lastFragment.width() - m_collapsedWhitespaceWidth);
zalan@apple.com99ff6302015-03-18 14:48:01 +0000494 }
495 // This fragment is collapsed completely. No run is needed.
496 return;
497 }
antti@apple.com56a81e52019-10-11 19:03:37 +0000498 Run& lastRun = runs.last();
499 if (m_lastFragment.isLastInRenderer() || m_lastFragment.isCollapsed() || fragment.isLineBreak() || lastRun.isLineBreak)
antti@apple.comb8db0352019-09-26 18:25:47 +0000500 runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false, fragment.hasHyphen(), fragment.isLineBreak()));
zalan@apple.com99ff6302015-03-18 14:48:01 +0000501 else {
zalan@apple.com99ff6302015-03-18 14:48:01 +0000502 lastRun.end = endPosition;
503 lastRun.logicalRight += fragment.width();
zalan@apple.com89b951f2017-01-24 21:38:20 +0000504 ASSERT(!lastRun.hasHyphen);
505 lastRun.hasHyphen = fragment.hasHyphen();
zalan@apple.com99ff6302015-03-18 14:48:01 +0000506 }
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000507 }
zalan@apple.combeef4862015-01-31 06:26:02 +0000508 m_runsWidth += fragment.width();
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000509 m_lastFragment = fragment;
510 if (m_fragments)
511 (*m_fragments).append(fragment);
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000512
jfernandez@igalia.com2c9d1252019-04-08 20:31:05 +0000513 if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) {
zalan@apple.combeef4862015-01-31 06:26:02 +0000514 m_trailingWhitespaceWidth += fragment.width();
jfernandez@igalia.com2c9d1252019-04-08 20:31:05 +0000515 m_lastWhitespaceFragment = fragment;
516 } else {
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000517 m_trailingWhitespaceWidth = 0;
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000518 m_lastNonWhitespaceFragment = fragment;
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000519 }
520
521 if (!m_firstCharacterFits)
zalan@apple.combeef4862015-01-31 06:26:02 +0000522 m_firstCharacterFits = fragment.start() + 1 > endPosition || m_runsWidth <= m_availableWidth;
zalan@apple.com35954352014-11-04 20:08:13 +0000523 }
524
zalan@apple.com99ff6302015-03-18 14:48:01 +0000525 TextFragmentIterator::TextFragment revertToLastCompleteFragment(Layout::RunVector& runs)
526 {
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000527 if (!m_uncompletedWidth) {
528 ASSERT(m_lastFragment == m_lastCompleteFragment);
529 return m_lastFragment;
zalan@apple.com99ff6302015-03-18 14:48:01 +0000530 }
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000531 ASSERT(m_lastFragment.isValid());
532 m_runsWidth -= m_uncompletedWidth;
zalan@apple.come3e62602017-02-03 21:17:28 +0000533 revertRuns(runs, endPositionForCollapsedFragment(m_lastCompleteFragment), m_uncompletedWidth);
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000534 m_uncompletedWidth = 0;
535 ASSERT(m_lastCompleteFragment.isValid());
zalan@apple.com99ff6302015-03-18 14:48:01 +0000536 return m_lastCompleteFragment;
537 }
538
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000539 void removeTrailingWhitespace(Layout::RunVector& runs)
540 {
antti@apple.com56a81e52019-10-11 19:03:37 +0000541 if (!hasTrailingWhitespace())
zalan@apple.com99ff6302015-03-18 14:48:01 +0000542 return;
zalan@apple.com539173e2017-03-07 21:02:15 +0000543 if (m_lastNonWhitespaceFragment) {
544 auto needsReverting = m_lastNonWhitespaceFragment->end() != m_lastFragment.end();
545 // Trailing whitespace fragment might actually have zero length.
546 ASSERT(needsReverting || !m_trailingWhitespaceWidth);
547 if (needsReverting) {
548 revertRuns(runs, m_lastNonWhitespaceFragment->end(), m_trailingWhitespaceWidth);
549 m_runsWidth -= m_trailingWhitespaceWidth;
550 }
551 m_trailingWhitespaceWidth = 0;
552 m_lastFragment = *m_lastNonWhitespaceFragment;
553 return;
554 }
555 // This line is all whitespace.
556 revertAllRunsOnCurrentLine(runs);
557 m_runsWidth = 0;
558 m_trailingWhitespaceWidth = 0;
559 // FIXME: Make m_lastFragment optional.
560 m_lastFragment = TextFragmentIterator::TextFragment();
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000561 }
zalan@apple.com5a093602015-01-22 21:54:53 +0000562
antti@apple.com56a81e52019-10-11 19:03:37 +0000563 float trailingWhitespaceWidth() const { return m_trailingWhitespaceWidth; }
564
zalan@apple.com5a093602015-01-22 21:54:53 +0000565private:
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000566 bool expansionOpportunity(TextFragmentIterator::TextFragment::Type currentFragmentType, TextFragmentIterator::TextFragment::Type previousFragmentType) const
567 {
568 return (currentFragmentType == TextFragmentIterator::TextFragment::Whitespace
569 || (currentFragmentType == TextFragmentIterator::TextFragment::NonWhitespace && previousFragmentType == TextFragmentIterator::TextFragment::NonWhitespace));
570 }
571
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000572 float m_availableWidth { 0 };
573 float m_logicalLeftOffset { 0 };
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000574 float m_runsWidth { 0 };
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000575 TextFragmentIterator::TextFragment m_overflowedFragment;
576 TextFragmentIterator::TextFragment m_lastFragment;
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000577 Optional<TextFragmentIterator::TextFragment> m_lastNonWhitespaceFragment;
jfernandez@igalia.com2c9d1252019-04-08 20:31:05 +0000578 Optional<TextFragmentIterator::TextFragment> m_lastWhitespaceFragment;
zalan@apple.com99ff6302015-03-18 14:48:01 +0000579 TextFragmentIterator::TextFragment m_lastCompleteFragment;
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000580 float m_uncompletedWidth { 0 };
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000581 float m_trailingWhitespaceWidth { 0 }; // Use this to remove trailing whitespace without re-mesuring the text.
zalan@apple.com99ff6302015-03-18 14:48:01 +0000582 float m_collapsedWhitespaceWidth { 0 };
zalan@apple.comc23decf2015-01-28 23:36:46 +0000583 // Having one character on the line does not necessarily mean it actually fits.
584 // First character of the first fragment might be forced on to the current line even if it does not fit.
585 bool m_firstCharacterFits { false };
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000586 bool m_hyphenationDisabled { false };
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000587 Optional<Vector<TextFragmentIterator::TextFragment, 30>> m_fragments;
zalan@apple.com6ab19072014-10-28 17:57:14 +0000588};
589
antti@apple.com56a81e52019-10-11 19:03:37 +0000590static float computeLineLeft(const LineState& line, TextAlignMode textAlign, float& hangingWhitespaceWidth)
591{
592 float totalWidth = line.width() - hangingWhitespaceWidth;
593 float remainingWidth = line.availableWidth() - totalWidth;
594 float left = line.logicalLeftOffset();
595 switch (textAlign) {
596 case TextAlignMode::Left:
597 case TextAlignMode::WebKitLeft:
598 case TextAlignMode::Start:
599 hangingWhitespaceWidth = std::max(0.f, std::min(hangingWhitespaceWidth, remainingWidth));
600 return left;
601 case TextAlignMode::Right:
602 case TextAlignMode::WebKitRight:
603 case TextAlignMode::End:
604 hangingWhitespaceWidth = 0;
605 return left + std::max<float>(remainingWidth, 0);
606 case TextAlignMode::Center:
607 case TextAlignMode::WebKitCenter:
608 hangingWhitespaceWidth = std::max(0.f, std::min(hangingWhitespaceWidth, (remainingWidth + 1) / 2));
609 return left + std::max<float>(remainingWidth / 2, 0);
610 case TextAlignMode::Justify:
611 ASSERT_NOT_REACHED();
612 break;
613 }
614 ASSERT_NOT_REACHED();
615 return 0;
616}
617
zalan@apple.com1802def2015-02-03 03:08:33 +0000618static bool preWrap(const TextFragmentIterator::Style& style)
zalan@apple.comf6bb5572015-01-23 06:26:28 +0000619{
antti@apple.com56a81e52019-10-11 19:03:37 +0000620 return style.wrapLines && !style.collapseWhitespace && !style.breakSpaces;
zalan@apple.com6ab19072014-10-28 17:57:14 +0000621}
622
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000623static void updateLineConstrains(const RenderBlockFlow& flow, LineState& line, const LineState& previousLine, unsigned& numberOfPrecedingLinesWithHyphen, const TextFragmentIterator::Style& style, bool isFirstLine)
zalan@apple.com6ab19072014-10-28 17:57:14 +0000624{
zalan@apple.com8d1dfe42015-11-20 19:20:57 +0000625 bool shouldApplyTextIndent = !flow.isAnonymous() || flow.parent()->firstChild() == &flow;
zalan@apple.com465ab402015-01-22 23:42:46 +0000626 LayoutUnit height = flow.logicalHeight();
zalan@apple.com0941e5f2017-12-04 22:52:26 +0000627 LayoutUnit logicalHeight = flow.minLineHeightForReplacedRenderer(false, 0);
ross.kirsling@sony.coma10d10c2018-11-23 20:47:11 +0000628 line.setLogicalLeftOffset(flow.logicalLeftOffsetForLine(height, DoNotIndentText, logicalHeight) + (shouldApplyTextIndent && isFirstLine ? flow.textIndentOffset() : 0_lu));
zalan@apple.com0941e5f2017-12-04 22:52:26 +0000629 float logicalRightOffset = flow.logicalRightOffsetForLine(height, DoNotIndentText, logicalHeight);
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000630 line.setAvailableWidth(std::max<float>(0, logicalRightOffset - line.logicalLeftOffset()));
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000631 if (style.textAlign == TextAlignMode::Justify)
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000632 line.setNeedsAllFragments();
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000633 numberOfPrecedingLinesWithHyphen = (previousLine.isEmpty() || !previousLine.lastFragment().hasHyphen()) ? 0 : numberOfPrecedingLinesWithHyphen + 1;
634 if (style.hyphenLimitLines && numberOfPrecedingLinesWithHyphen >= *style.hyphenLimitLines)
635 line.setHyphenationDisabled();
zalan@apple.comc934a4b2017-05-16 19:01:15 +0000636 line.setCollapedWhitespaceWidth(style.font.spaceWidth() + style.wordSpacing);
zalan@apple.com465ab402015-01-22 23:42:46 +0000637}
638
zalan@apple.com31016952017-04-22 14:34:35 +0000639struct SplitFragmentData {
640 unsigned position;
641 float width;
642};
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000643static Optional<unsigned> hyphenPositionForFragment(SplitFragmentData splitData, const TextFragmentIterator::TextFragment& fragmentToSplit,
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000644 const LineState& line, const TextFragmentIterator& textFragmentIterator, float availableWidth)
zalan@apple.com9d9afbf2017-01-26 21:19:38 +0000645{
646 auto& style = textFragmentIterator.style();
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000647 if (!style.shouldHyphenate || line.isHyphenationDisabled())
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000648 return WTF::nullopt;
zalan@apple.com9d9afbf2017-01-26 21:19:38 +0000649
zalan@apple.com7baecc12017-03-14 21:58:39 +0000650 // FIXME: This is a workaround for webkit.org/b/169613. See maxPrefixWidth computation in tryHyphenating().
651 // It does not work properly with non-collapsed leading tabs when font is enlarged.
652 auto adjustedAvailableWidth = availableWidth - style.hyphenStringWidth;
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000653 if (!line.isEmpty())
zalan@apple.com3c442032017-05-08 18:38:20 +0000654 adjustedAvailableWidth += style.font.spaceWidth();
zalan@apple.com7baecc12017-03-14 21:58:39 +0000655 if (!enoughWidthForHyphenation(adjustedAvailableWidth, style.font.pixelSize()))
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000656 return WTF::nullopt;
zalan@apple.com9d9afbf2017-01-26 21:19:38 +0000657
658 // We might be able to fit the hyphen at the split position.
zalan@apple.com31016952017-04-22 14:34:35 +0000659 auto splitPositionWithHyphen = splitData.position;
zalan@apple.com9d9afbf2017-01-26 21:19:38 +0000660 // Find a splitting position where hyphen surely fits.
661 unsigned start = fragmentToSplit.start();
zalan@apple.com31016952017-04-22 14:34:35 +0000662 auto leftSideWidth = splitData.width;
zalan@apple.com9d9afbf2017-01-26 21:19:38 +0000663 while (leftSideWidth + style.hyphenStringWidth > availableWidth) {
664 if (--splitPositionWithHyphen <= start)
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000665 return WTF::nullopt; // No space for hyphen.
zalan@apple.com9d9afbf2017-01-26 21:19:38 +0000666 leftSideWidth -= textFragmentIterator.textWidth(splitPositionWithHyphen, splitPositionWithHyphen + 1, 0);
667 }
668 ASSERT(splitPositionWithHyphen > start);
669 return textFragmentIterator.lastHyphenPosition(fragmentToSplit, splitPositionWithHyphen + 1);
670}
671
zalan@apple.com31016952017-04-22 14:34:35 +0000672static SplitFragmentData split(const TextFragmentIterator::TextFragment& fragment, float availableWidth,
673 const TextFragmentIterator& textFragmentIterator)
674{
675 ASSERT(availableWidth >= 0);
676 auto left = fragment.start();
677 // Pathological case of (extremely)long string and narrow lines.
678 // Adjust the range so that we can pick a reasonable midpoint.
679 auto averageCharacterWidth = fragment.width() / fragment.length();
680 auto right = std::min<unsigned>(left + (2 * availableWidth / averageCharacterWidth), fragment.end() - 1);
681 // Preserve the left width for the final split position so that we don't need to remeasure the left side again.
682 float leftSideWidth = 0;
683 while (left < right) {
684 auto middle = (left + right) / 2;
685 auto width = textFragmentIterator.textWidth(fragment.start(), middle + 1, 0);
686 if (width < availableWidth) {
687 left = middle + 1;
688 leftSideWidth = width;
689 } else if (width > availableWidth)
690 right = middle;
691 else {
692 right = middle + 1;
693 leftSideWidth = width;
694 break;
695 }
696 }
697 return { right, leftSideWidth };
698}
699
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000700static TextFragmentIterator::TextFragment splitFragmentToFitLine(TextFragmentIterator::TextFragment& fragmentToSplit,
701 const LineState& line, const TextFragmentIterator& textFragmentIterator)
zalan@apple.com6ab19072014-10-28 17:57:14 +0000702{
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000703 auto availableWidth = line.availableWidth() - line.width();
zalan@apple.com31016952017-04-22 14:34:35 +0000704 auto splitFragmentData = split(fragmentToSplit, availableWidth, textFragmentIterator);
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000705 Optional<unsigned> hyphenPosition = WTF::nullopt;
zalan@apple.com89b951f2017-01-24 21:38:20 +0000706 // Does first character fit this line?
zalan@apple.com31016952017-04-22 14:34:35 +0000707 if (splitFragmentData.position == fragmentToSplit.start()) {
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000708 // Keep at least one character on empty lines.
709 if (line.isEmpty())
zalan@apple.com31016952017-04-22 14:34:35 +0000710 splitFragmentData.width = textFragmentIterator.textWidth(fragmentToSplit.start(), ++splitFragmentData.position, 0);
711 } else {
712 hyphenPosition = hyphenPositionForFragment(splitFragmentData, fragmentToSplit, line, textFragmentIterator, availableWidth);
713 if (hyphenPosition) {
714 splitFragmentData.position = *hyphenPosition;
715 splitFragmentData.width = textFragmentIterator.textWidth(fragmentToSplit.start(), splitFragmentData.position, 0);
716 }
717 }
718 // If the right side surely does not fit the (next)line, we don't need the width to be kerning/ligature adjusted.
719 // Part of it gets re-measured as the left side during next split.
720 // This saves measuring long chunk of text repeatedly (see pathological case at ::split).
721 auto rightSideWidth = fragmentToSplit.width() - splitFragmentData.width;
722 if (rightSideWidth < 2 * availableWidth)
723 rightSideWidth = textFragmentIterator.textWidth(splitFragmentData.position, fragmentToSplit.end(), 0);
zalan@apple.com210419f2017-05-14 21:16:42 +0000724 return hyphenPosition ? fragmentToSplit.splitWithHyphen(splitFragmentData.position, textFragmentIterator.style().hyphenStringWidth,
725 splitFragmentData.width, rightSideWidth) : fragmentToSplit.split(splitFragmentData.position, splitFragmentData.width, rightSideWidth);
zalan@apple.com6ab19072014-10-28 17:57:14 +0000726}
727
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000728enum PreWrapLineBreakRule { Preserve, Ignore };
729
730static TextFragmentIterator::TextFragment consumeLineBreakIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator, LineState& line, Layout::RunVector& runs,
731 PreWrapLineBreakRule preWrapLineBreakRule = PreWrapLineBreakRule::Preserve)
732{
733 if (!fragment.isLineBreak())
734 return fragment;
735
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000736 bool isHardLinebreak = fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak;
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000737 // <br> always produces a run. (required by testing output)
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000738 if (isHardLinebreak)
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000739 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000740
741 auto& style = textFragmentIterator.style();
742 if (style.preserveNewline && preWrapLineBreakRule == PreWrapLineBreakRule::Preserve) {
743 if (!isHardLinebreak)
744 return fragment;
745 }
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000746 return textFragmentIterator.nextTextFragment();
747}
748
749static TextFragmentIterator::TextFragment skipWhitespaceIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator)
750{
751 if (!textFragmentIterator.style().collapseWhitespace)
752 return fragment;
753
754 TextFragmentIterator::TextFragment firstNonWhitespaceFragment = fragment;
755 while (firstNonWhitespaceFragment.type() == TextFragmentIterator::TextFragment::Whitespace)
756 firstNonWhitespaceFragment = textFragmentIterator.nextTextFragment();
757 return firstNonWhitespaceFragment;
758}
759
760static TextFragmentIterator::TextFragment firstFragment(TextFragmentIterator& textFragmentIterator, LineState& currentLine, const LineState& previousLine, Layout::RunVector& runs)
zalan@apple.comee6297d2015-01-27 17:01:07 +0000761{
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000762 // Handle overflow fragment from previous line.
763 auto overflowedFragment = previousLine.overflowedFragment();
764 if (overflowedFragment.isEmpty())
765 return skipWhitespaceIfNeeded(textFragmentIterator.nextTextFragment(), textFragmentIterator);
zalan@apple.comee6297d2015-01-27 17:01:07 +0000766
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000767 if (overflowedFragment.type() != TextFragmentIterator::TextFragment::Whitespace)
768 return overflowedFragment;
769
770 // Leading whitespace handling.
771 auto& style = textFragmentIterator.style();
jfernandez@igalia.com2c9d1252019-04-08 20:31:05 +0000772 if (style.breakSpaces) {
773 // Leading whitespace created after breaking the previous line.
774 // Breaking before the first space after a word is only allowed in combination with break-all or break-word.
775 if (style.breakFirstWordOnOverflow || previousLine.hasTrailingWhitespace())
776 return overflowedFragment;
777 }
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000778 // Special overflow pre-wrap whitespace handling: skip the overflowed whitespace (even when style says not-collapsible)
779 // if we manage to fit at least one character on the previous line.
antti@apple.com56a81e52019-10-11 19:03:37 +0000780 if ((style.collapseWhitespace || style.wrapLines) && previousLine.firstCharacterFits()) {
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000781 // If skipping the whitespace puts us on a newline, skip the newline too as we already wrapped the line.
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000782 auto firstFragmentCandidate = consumeLineBreakIfNeeded(textFragmentIterator.nextTextFragment(), textFragmentIterator, currentLine, runs,
antti@apple.com56a81e52019-10-11 19:03:37 +0000783 preWrap(style) ? PreWrapLineBreakRule::Ignore : PreWrapLineBreakRule::Preserve);
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000784 return skipWhitespaceIfNeeded(firstFragmentCandidate, textFragmentIterator);
zalan@apple.comee6297d2015-01-27 17:01:07 +0000785 }
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000786 return skipWhitespaceIfNeeded(overflowedFragment, textFragmentIterator);
zalan@apple.comee6297d2015-01-27 17:01:07 +0000787}
788
zalan@apple.com99ff6302015-03-18 14:48:01 +0000789static void forceFragmentToLine(LineState& line, TextFragmentIterator& textFragmentIterator, Layout::RunVector& runs, const TextFragmentIterator::TextFragment& fragment)
790{
791 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
792 // Check if there are more fragments to add to the current line.
zalan@apple.com99ff6302015-03-18 14:48:01 +0000793 auto nextFragment = textFragmentIterator.nextTextFragment();
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000794 if (fragment.overlapsToNextRenderer()) {
795 while (true) {
796 if (nextFragment.type() != fragment.type())
797 break;
798 line.appendFragmentAndCreateRunIfNeeded(nextFragment, runs);
799 // Does it overlap to the next segment?
800 if (!nextFragment.overlapsToNextRenderer())
801 return;
802 nextFragment = textFragmentIterator.nextTextFragment();
803 }
zalan@apple.com99ff6302015-03-18 14:48:01 +0000804 }
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000805 // 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.
806 nextFragment = skipWhitespaceIfNeeded(nextFragment, textFragmentIterator);
807 nextFragment = consumeLineBreakIfNeeded(nextFragment, textFragmentIterator, line, runs);
zalan@apple.com99ff6302015-03-18 14:48:01 +0000808 line.setOverflowedFragment(nextFragment);
809}
810
zalan@apple.com1802def2015-02-03 03:08:33 +0000811static bool createLineRuns(LineState& line, const LineState& previousLine, Layout::RunVector& runs, TextFragmentIterator& textFragmentIterator)
zalan@apple.com6ab19072014-10-28 17:57:14 +0000812{
zalan@apple.com1802def2015-02-03 03:08:33 +0000813 const auto& style = textFragmentIterator.style();
zalan@apple.com7dae84072016-01-13 17:08:35 +0000814 bool lineCanBeWrapped = style.wrapLines || style.breakFirstWordOnOverflow || style.breakAnyWordOnOverflow;
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000815 auto fragment = firstFragment(textFragmentIterator, line, previousLine, runs);
zalan@apple.com1802def2015-02-03 03:08:33 +0000816 while (fragment.type() != TextFragmentIterator::TextFragment::ContentEnd) {
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000817 // Hard and soft linebreaks.
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000818 if (fragment.isLineBreak()) {
zalan@apple.comee6297d2015-01-27 17:01:07 +0000819 // 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.)
zalan@apple.comaeb70812020-01-05 19:23:13 +0000820 if (line.isEmpty() || fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak || preWrap(style) || style.preserveNewline) {
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000821 if (style.textAlign == TextAlignMode::Right || style.textAlign == TextAlignMode::WebKitRight)
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000822 line.removeTrailingWhitespace(runs);
zalan@apple.com99ff6302015-03-18 14:48:01 +0000823 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
zalan@apple.com8bf2a912015-04-10 03:15:50 +0000824 }
zalan@apple.com62e70882015-01-24 01:13:44 +0000825 break;
826 }
zalan@apple.combeef4862015-01-31 06:26:02 +0000827 if (lineCanBeWrapped && !line.fits(fragment.width())) {
zalan@apple.com62e70882015-01-24 01:13:44 +0000828 // Overflow wrapping behaviour:
829 // 1. Whitesapce collapse on: whitespace is skipped. Jump to next line.
830 // 2. Whitespace collapse off: whitespace is wrapped.
831 // 3. First, non-whitespace fragment is either wrapped or kept on the line. (depends on overflow-wrap)
zalan@apple.com7dae84072016-01-13 17:08:35 +0000832 // 5. Non-whitespace fragment when there's already another fragment on the line either gets wrapped (word-break: break-all)
833 // or gets pushed to the next line.
zalan@apple.com99ff6302015-03-18 14:48:01 +0000834 bool emptyLine = line.isEmpty();
zalan@apple.com62e70882015-01-24 01:13:44 +0000835 // Whitespace fragment.
zalan@apple.com1802def2015-02-03 03:08:33 +0000836 if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) {
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000837 if (style.collapseWhitespace) {
838 // Push collapased whitespace to the next line.
839 line.setOverflowedFragment(fragment);
840 break;
zalan@apple.com62e70882015-01-24 01:13:44 +0000841 }
jfernandez@igalia.comd6823fd2019-04-29 19:56:56 +0000842 if (style.breakSpaces && line.hasWhitespaceFragments() && fragment.length() == 1) {
jfernandez@igalia.com2c9d1252019-04-08 20:31:05 +0000843 // Breaking before the first space after a word is not allowed if there are previous breaking opportunities in the line.
844 textFragmentIterator.revertToEndOfFragment(line.revertToLastCompleteFragment(runs));
845 break;
846 }
antti@apple.com56a81e52019-10-11 19:03:37 +0000847 if (preWrap(style)) {
848 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
849 fragment = textFragmentIterator.nextTextFragment(line.width());
850 if (fragment.isLineBreak())
851 continue;
852 line.setOverflowedFragment(fragment);
853 break;
854 }
zalan@apple.com79e83ab2017-05-15 14:21:54 +0000855 // Split the whitespace; left part stays on this line, right is pushed to next line.
856 line.setOverflowedFragment(splitFragmentToFitLine(fragment, line, textFragmentIterator));
857 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
zalan@apple.com62e70882015-01-24 01:13:44 +0000858 break;
zalan@apple.com6ab19072014-10-28 17:57:14 +0000859 }
zalan@apple.com62e70882015-01-24 01:13:44 +0000860 // Non-whitespace fragment. (!style.wrapLines: bug138102(preserve existing behavior)
zalan@apple.com7dae84072016-01-13 17:08:35 +0000861 if (((emptyLine && style.breakFirstWordOnOverflow) || style.breakAnyWordOnOverflow) || !style.wrapLines) {
zalan@apple.comee6297d2015-01-27 17:01:07 +0000862 // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line.
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000863 line.setOverflowedFragment(splitFragmentToFitLine(fragment, line, textFragmentIterator));
zalan@apple.com99ff6302015-03-18 14:48:01 +0000864 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
zalan@apple.com62e70882015-01-24 01:13:44 +0000865 break;
866 }
zalan@apple.com99ff6302015-03-18 14:48:01 +0000867 ASSERT(fragment.type() == TextFragmentIterator::TextFragment::NonWhitespace);
zalan@apple.com89b951f2017-01-24 21:38:20 +0000868 // Find out if this non-whitespace fragment has a hyphen where we can break.
869 if (style.shouldHyphenate) {
870 auto fragmentToSplit = fragment;
871 // Split and check if we actually ended up with a hyphen.
zalan@apple.com8bfb3f72017-03-16 22:14:30 +0000872 auto overflowFragment = splitFragmentToFitLine(fragmentToSplit, line, textFragmentIterator);
zalan@apple.com89b951f2017-01-24 21:38:20 +0000873 if (fragmentToSplit.hasHyphen()) {
874 line.setOverflowedFragment(overflowFragment);
875 line.appendFragmentAndCreateRunIfNeeded(fragmentToSplit, runs);
876 break;
877 }
878 // No hyphen, no split.
879 }
880 // Non-breakable non-whitespace first fragment. Add it to the current line. -it overflows though.
zalan@apple.com62e70882015-01-24 01:13:44 +0000881 if (emptyLine) {
zalan@apple.com99ff6302015-03-18 14:48:01 +0000882 forceFragmentToLine(line, textFragmentIterator, runs, fragment);
zalan@apple.com62e70882015-01-24 01:13:44 +0000883 break;
884 }
885 // Non-breakable non-whitespace fragment when there's already content on the line. Push it to the next line.
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000886 ASSERT(line.lastFragment().isValid());
887 if (line.lastFragment().overlapsToNextRenderer()) {
zalan@apple.com99ff6302015-03-18 14:48:01 +0000888 // Check if this fragment is a continuation of a previous segment. In such cases, we need to remove them all.
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000889 textFragmentIterator.revertToEndOfFragment(line.revertToLastCompleteFragment(runs));
zalan@apple.com99ff6302015-03-18 14:48:01 +0000890 break;
891 }
zalan@apple.com58dc70b2015-01-31 03:37:46 +0000892 line.setOverflowedFragment(fragment);
zalan@apple.com6ab19072014-10-28 17:57:14 +0000893 break;
894 }
zalan@apple.com99ff6302015-03-18 14:48:01 +0000895 line.appendFragmentAndCreateRunIfNeeded(fragment, runs);
zalan@apple.comee6297d2015-01-27 17:01:07 +0000896 // Find the next text fragment.
zalan@apple.com1802def2015-02-03 03:08:33 +0000897 fragment = textFragmentIterator.nextTextFragment(line.width());
zalan@apple.com6ab19072014-10-28 17:57:14 +0000898 }
zalan@apple.com99ff6302015-03-18 14:48:01 +0000899 return (fragment.type() == TextFragmentIterator::TextFragment::ContentEnd && line.overflowedFragment().isEmpty()) || line.overflowedFragment().type() == TextFragmentIterator::TextFragment::ContentEnd;
zalan@apple.com35954352014-11-04 20:08:13 +0000900}
901
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000902static ExpansionBehavior expansionBehavior(bool isAfterExpansion, bool lastRunOnLine)
zalan@apple.com35954352014-11-04 20:08:13 +0000903{
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000904 ExpansionBehavior expansionBehavior;
mmaxfield@apple.comc01fa5a2020-08-06 18:03:01 +0000905 expansionBehavior = isAfterExpansion ? ForbidLeftExpansion : AllowLeftExpansion;
906 expansionBehavior |= lastRunOnLine ? ForbidRightExpansion : AllowRightExpansion;
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000907 return expansionBehavior;
908}
909
zalan@apple.com2be86792016-04-07 14:55:54 +0000910static void justifyRuns(const LineState& line, Layout::RunVector& runs, unsigned firstRunIndex)
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000911{
zalan@apple.comee6297d2015-01-27 17:01:07 +0000912 ASSERT(runs.size());
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000913 auto widthToDistribute = line.availableWidth() - line.width();
914 if (widthToDistribute <= 0)
915 return;
916
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000917 auto lastRunIndex = runs.size() - 1;
918 ASSERT(firstRunIndex <= lastRunIndex);
919 Vector<std::pair<unsigned, ExpansionBehavior>> expansionOpportunityList;
920 unsigned expansionOpportunityCountOnThisLine = 0;
921 auto isAfterExpansion = true;
922 for (auto i = firstRunIndex; i <= lastRunIndex; ++i) {
923 const auto& run = runs.at(i);
924 unsigned opportunityCountInRun = 0;
925 std::tie(opportunityCountInRun, isAfterExpansion) = line.expansionOpportunityCount(run.start, run.end);
926 expansionOpportunityList.append(std::make_pair(opportunityCountInRun, expansionBehavior(isAfterExpansion, i == lastRunIndex)));
927 expansionOpportunityCountOnThisLine += opportunityCountInRun;
928 }
929 if (!expansionOpportunityCountOnThisLine)
930 return;
931
932 ASSERT(expansionOpportunityList.size() == lastRunIndex - firstRunIndex + 1);
933 auto expansion = widthToDistribute / expansionOpportunityCountOnThisLine;
934 float accumulatedExpansion = 0;
935 for (auto i = firstRunIndex; i <= lastRunIndex; ++i) {
936 auto& run = runs.at(i);
937 unsigned opportunityCountInRun;
938 std::tie(opportunityCountInRun, run.expansionBehavior) = expansionOpportunityList.at(i - firstRunIndex);
939 run.expansion = opportunityCountInRun * expansion;
940 run.logicalLeft += accumulatedExpansion;
941 run.logicalRight += (accumulatedExpansion + run.expansion);
942 accumulatedExpansion += run.expansion;
943 }
944}
945
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000946static TextAlignMode textAlignForLine(const TextFragmentIterator::Style& style, bool lastLine)
zalan@apple.com2be86792016-04-07 14:55:54 +0000947{
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000948 // 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.
zalan@apple.com2be86792016-04-07 14:55:54 +0000949 auto textAlign = style.textAlign;
commit-queue@webkit.orgeea2d6a2018-05-25 01:42:36 +0000950 if (textAlign == TextAlignMode::Justify && (!style.collapseWhitespace || lastLine))
951 textAlign = TextAlignMode::Left;
zalan@apple.com2be86792016-04-07 14:55:54 +0000952 return textAlign;
953}
954
cdumez@apple.com8b7a0222018-12-20 04:41:11 +0000955static void closeLineEndingAndAdjustRuns(LineState& line, Layout::RunVector& runs, Optional<unsigned> lastRunIndexOfPreviousLine, unsigned& lineCount,
zalan@apple.com2be86792016-04-07 14:55:54 +0000956 const TextFragmentIterator& textFragmentIterator, bool lastLineInFlow)
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000957{
958 if (!runs.size() || (lastRunIndexOfPreviousLine && runs.size() - 1 == lastRunIndexOfPreviousLine.value()))
959 return;
antti@apple.com56a81e52019-10-11 19:03:37 +0000960
961 const auto& style = textFragmentIterator.style();
962
963 if (style.collapseWhitespace)
964 line.removeTrailingWhitespace(runs);
965
zalan@apple.comee6297d2015-01-27 17:01:07 +0000966 if (!runs.size())
967 return;
antti@apple.com56a81e52019-10-11 19:03:37 +0000968
zalan@apple.com35954352014-11-04 20:08:13 +0000969 // Adjust runs' position by taking line's alignment into account.
zalan@apple.com2be86792016-04-07 14:55:54 +0000970 auto firstRunIndex = lastRunIndexOfPreviousLine ? lastRunIndexOfPreviousLine.value() + 1 : 0;
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000971 auto lineLogicalLeft = line.logicalLeftOffset();
zalan@apple.com5c6a0752017-02-01 00:01:26 +0000972 auto textAlign = textAlignForLine(style, lastLineInFlow || (line.lastFragment().isValid() && line.lastFragment().type() == TextFragmentIterator::TextFragment::HardLineBreak));
antti@apple.com56a81e52019-10-11 19:03:37 +0000973
974 // https://www.w3.org/TR/css-text-3/#white-space-phase-2
975 bool shouldHangTrailingWhitespace = style.wrapLines && line.trailingWhitespaceWidth();
976 auto hangingWhitespaceWidth = shouldHangTrailingWhitespace ? line.trailingWhitespaceWidth() : 0;
977
978 if (textAlign == TextAlignMode::Justify) {
zalan@apple.com2be86792016-04-07 14:55:54 +0000979 justifyRuns(line, runs, firstRunIndex);
antti@apple.com56a81e52019-10-11 19:03:37 +0000980 hangingWhitespaceWidth = 0;
981 } else
982 lineLogicalLeft = computeLineLeft(line, textAlign, hangingWhitespaceWidth);
983
zalan@apple.com6d7b01b2016-03-11 04:03:32 +0000984 for (auto i = firstRunIndex; i < runs.size(); ++i) {
985 runs[i].logicalLeft += lineLogicalLeft;
986 runs[i].logicalRight += lineLogicalLeft;
zalan@apple.com6ab19072014-10-28 17:57:14 +0000987 }
antti@apple.com56a81e52019-10-11 19:03:37 +0000988
989 if (shouldHangTrailingWhitespace && hangingWhitespaceWidth < line.trailingWhitespaceWidth())
990 runs.last().logicalRight = runs.last().logicalRight - (line.trailingWhitespaceWidth() - hangingWhitespaceWidth);
991
zalan@apple.comee6297d2015-01-27 17:01:07 +0000992 runs.last().isEndOfLine = true;
zalan@apple.com35954352014-11-04 20:08:13 +0000993 ++lineCount;
zalan@apple.com6ab19072014-10-28 17:57:14 +0000994}
995
zalan@apple.com484bc672014-11-17 21:14:57 +0000996static void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsigned& lineCount)
antti@apple.com940f5872013-10-24 20:31:11 +0000997{
antti@apple.com01a04122013-11-20 21:23:19 +0000998 LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore();
999 LayoutUnit lineHeight = lineHeightFromFlow(flow);
zalan@apple.comee6297d2015-01-27 17:01:07 +00001000 LineState line;
zalan@apple.com8bfb3f72017-03-16 22:14:30 +00001001 unsigned numberOfPrecedingLinesWithHyphen = 0;
zalan@apple.com35954352014-11-04 20:08:13 +00001002 bool isEndOfContent = false;
zalan@apple.com1802def2015-02-03 03:08:33 +00001003 TextFragmentIterator textFragmentIterator = TextFragmentIterator(flow);
cdumez@apple.com8b7a0222018-12-20 04:41:11 +00001004 Optional<unsigned> lastRunIndexOfPreviousLine;
zalan@apple.com6ab19072014-10-28 17:57:14 +00001005 do {
antti@apple.com01a04122013-11-20 21:23:19 +00001006 flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore);
zalan@apple.comee6297d2015-01-27 17:01:07 +00001007 LineState previousLine = line;
zalan@apple.comee6297d2015-01-27 17:01:07 +00001008 line = LineState();
zalan@apple.com8bfb3f72017-03-16 22:14:30 +00001009 updateLineConstrains(flow, line, previousLine, numberOfPrecedingLinesWithHyphen, textFragmentIterator.style(), !lineCount);
zalan@apple.com1802def2015-02-03 03:08:33 +00001010 isEndOfContent = createLineRuns(line, previousLine, runs, textFragmentIterator);
zalan@apple.com6d7b01b2016-03-11 04:03:32 +00001011 closeLineEndingAndAdjustRuns(line, runs, lastRunIndexOfPreviousLine, lineCount, textFragmentIterator, isEndOfContent);
1012 if (runs.size())
1013 lastRunIndexOfPreviousLine = runs.size() - 1;
zalan@apple.com35954352014-11-04 20:08:13 +00001014 } while (!isEndOfContent);
antti@apple.comaa930a62013-11-08 12:53:19 +00001015}
1016
antti@apple.comba6789f2019-09-23 15:54:58 +00001017Ref<Layout> create(RenderBlockFlow& flow)
antti@apple.comaa930a62013-11-08 12:53:19 +00001018{
antti@apple.comaa930a62013-11-08 12:53:19 +00001019 unsigned lineCount = 0;
zalan@apple.comeb1c0302014-11-05 03:47:45 +00001020 Layout::RunVector runs;
zalan@apple.com484bc672014-11-17 21:14:57 +00001021 createTextRuns(runs, flow, lineCount);
zalan@apple.com6cda0fa2018-05-09 00:20:18 +00001022 return Layout::create(runs, lineCount, flow);
antti@apple.comafe9c572013-10-29 20:25:44 +00001023}
1024
antti@apple.comba6789f2019-09-23 15:54:58 +00001025Ref<Layout> Layout::create(const RunVector& runVector, unsigned lineCount, const RenderBlockFlow& blockFlow)
antti@apple.comafe9c572013-10-29 20:25:44 +00001026{
1027 void* slot = WTF::fastMalloc(sizeof(Layout) + sizeof(Run) * runVector.size());
antti@apple.comba6789f2019-09-23 15:54:58 +00001028 return adoptRef(*new (NotNull, slot) Layout(runVector, lineCount, blockFlow));
antti@apple.comafe9c572013-10-29 20:25:44 +00001029}
1030
zalan@apple.com6cda0fa2018-05-09 00:20:18 +00001031Layout::Layout(const RunVector& runVector, unsigned lineCount, const RenderBlockFlow& blockFlow)
antti@apple.com09c83512013-10-30 14:12:32 +00001032 : m_lineCount(lineCount)
1033 , m_runCount(runVector.size())
zalan@apple.com6cda0fa2018-05-09 00:20:18 +00001034 , m_blockFlowRenderer(blockFlow)
antti@apple.comafe9c572013-10-29 20:25:44 +00001035{
antti@apple.com09c83512013-10-30 14:12:32 +00001036 memcpy(m_runs, runVector.data(), m_runCount * sizeof(Run));
antti@apple.com940f5872013-10-24 20:31:11 +00001037}
1038
zalan@apple.com6cda0fa2018-05-09 00:20:18 +00001039const RunResolver& Layout::runResolver() const
1040{
1041 if (!m_runResolver)
ysuzuki@apple.com1d8e24d2019-08-19 06:59:40 +00001042 m_runResolver = makeUnique<RunResolver>(m_blockFlowRenderer, *this);
zalan@apple.com6cda0fa2018-05-09 00:20:18 +00001043 return *m_runResolver;
1044}
1045
commit-queue@webkit.org01b4c142018-04-11 20:29:49 +00001046Layout::~Layout()
1047{
1048 simpleLineLayoutWillBeDeleted(*this);
1049}
1050
antti@apple.com940f5872013-10-24 20:31:11 +00001051}
1052}