diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2016-04-10 09:28:39 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2016-04-10 09:28:39 +0000 |
commit | 32761a6cee1d0dee366b885b7b9c777e67885688 (patch) | |
tree | d6bec92bebfb216f4126356e55518842c2f476a1 /Source/WebCore/rendering/SimpleLineLayout.cpp | |
parent | a4e969f4965059196ca948db781e52f7cfebf19e (diff) | |
download | WebKitGtk-tarball-32761a6cee1d0dee366b885b7b9c777e67885688.tar.gz |
webkitgtk-2.4.11webkitgtk-2.4.11
Diffstat (limited to 'Source/WebCore/rendering/SimpleLineLayout.cpp')
-rw-r--r-- | Source/WebCore/rendering/SimpleLineLayout.cpp | 1234 |
1 files changed, 318 insertions, 916 deletions
diff --git a/Source/WebCore/rendering/SimpleLineLayout.cpp b/Source/WebCore/rendering/SimpleLineLayout.cpp index f35f61465..b0f0b31af 100644 --- a/Source/WebCore/rendering/SimpleLineLayout.cpp +++ b/Source/WebCore/rendering/SimpleLineLayout.cpp @@ -35,113 +35,28 @@ #include "HitTestResult.h" #include "InlineTextBox.h" #include "LineWidth.h" -#include "Logging.h" #include "PaintInfo.h" #include "RenderBlockFlow.h" -#include "RenderChildIterator.h" -#include "RenderLineBreak.h" #include "RenderStyle.h" #include "RenderText.h" #include "RenderTextControl.h" #include "RenderView.h" #include "Settings.h" -#include "SimpleLineLayoutFlowContents.h" #include "SimpleLineLayoutFunctions.h" -#include "SimpleLineLayoutTextFragmentIterator.h" #include "Text.h" #include "TextPaintStyle.h" -#include "TextStream.h" +#include "break_lines.h" +#include <wtf/unicode/Unicode.h> namespace WebCore { namespace SimpleLineLayout { -#ifndef NDEBUG -void printSimpleLineLayoutCoverage(); -void printSimpleLineLayoutBlockList(); -void toggleSimpleLineLayout(); -#endif - -enum AvoidanceReason_ : uint64_t { - FlowIsInsideRegion = 1LLU << 0, - FlowHasHorizonalWritingMode = 1LLU << 1, - FlowHasOutline = 1LLU << 2, - FlowIsRuby = 1LLU << 3, - FlowIsPaginated = 1LLU << 4, - FlowHasTextOverflow = 1LLU << 5, - FlowIsDepricatedFlexBox = 1LLU << 6, - FlowParentIsPlaceholderElement = 1LLU << 7, - FlowParentIsTextAreaWithWrapping = 1LLU << 8, - FlowHasNonSupportedChild = 1LLU << 9, - FlowHasUnsupportedFloat = 1LLU << 10, - FlowHasUnsupportedUnderlineDecoration = 1LLU << 11, - FlowIsJustifyAligned = 1LLU << 12, - FlowHasOverflowVisible = 1LLU << 13, - FlowIsNotLTR = 1LLU << 14, - FlowHasLineBoxContainProperty = 1LLU << 15, - FlowIsNotTopToBottom = 1LLU << 16, - FlowHasLineBreak = 1LLU << 17, - FlowHasNonNormalUnicodeBiDi = 1LLU << 18, - FlowHasRTLOrdering = 1LLU << 19, - FlowHasLineAlignEdges = 1LLU << 20, - FlowHasLineSnap = 1LLU << 21, - FlowHasHypensAuto = 1LLU << 22, - FlowHasTextEmphasisFillOrMark = 1LLU << 23, - FlowHasTextShadow = 1LLU << 24, - FlowHasPseudoFirstLine = 1LLU << 25, - FlowHasPseudoFirstLetter = 1LLU << 26, - FlowHasTextCombine = 1LLU << 27, - FlowHasTextFillBox = 1LLU << 28, - FlowHasBorderFitLines = 1LLU << 29, - FlowHasNonAutoLineBreak = 1LLU << 30, - FlowHasNonAutoTrailingWord = 1LLU << 31, - FlowHasSVGFont = 1LLU << 32, - FlowTextIsEmpty = 1LLU << 33, - FlowTextHasNoBreakSpace = 1LLU << 34, - FlowTextHasSoftHyphen = 1LLU << 35, - FlowTextHasDirectionCharacter = 1LLU << 36, - FlowIsMissingPrimaryFont = 1LLU << 37, - FlowFontIsMissingGlyph = 1LLU << 38, - FlowTextIsCombineText = 1LLU << 39, - FlowTextIsRenderCounter = 1LLU << 40, - FlowTextIsRenderQuote = 1LLU << 41, - FlowTextIsTextFragment = 1LLU << 42, - FlowTextIsSVGInlineText = 1LLU << 43, - FlowFontIsNotSimple = 1LLU << 44, - FeatureIsDisabled = 1LLU << 45, - FlowHasNoParent = 1LLU << 46, - FlowHasNoChild = 1LLU << 47, - FlowChildIsSelected = 1LLU << 48, - EndOfReasons = 1LLU << 49 -}; -const unsigned NoReason = 0; - -typedef uint64_t AvoidanceReason; -typedef uint64_t AvoidanceReasonFlags; - -enum class IncludeReasons { First , All }; - -#ifndef NDEBUG -#define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \ - reasons |= reason; \ - if (includeReasons == IncludeReasons::First) \ - return reasons; \ - } -#else -#define SET_REASON_AND_RETURN_IF_NEEDED(reason, reasons, includeReasons) { \ - ASSERT_UNUSED(includeReasons, includeReasons == IncludeReasons::First); \ - reasons |= reason; \ - return reasons; \ - } -#endif - template <typename CharacterType> -static AvoidanceReasonFlags canUseForText(const CharacterType* text, unsigned length, const Font& font, IncludeReasons includeReasons) +static bool canUseForText(const CharacterType* text, unsigned length, const SimpleFontData& fontData) { - AvoidanceReasonFlags reasons = { }; // FIXME: <textarea maxlength=0> generates empty text node. if (!length) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsEmpty, reasons, includeReasons); - + return false; for (unsigned i = 0; i < length; ++i) { UChar character = text[i]; if (character == ' ') @@ -149,623 +64,409 @@ static AvoidanceReasonFlags canUseForText(const CharacterType* text, unsigned le // These would be easy to support. if (character == noBreakSpace) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasNoBreakSpace, reasons, includeReasons); + return false; if (character == softHyphen) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasSoftHyphen, reasons, includeReasons); + return false; UCharDirection direction = u_charDirection(character); if (direction == U_RIGHT_TO_LEFT || direction == U_RIGHT_TO_LEFT_ARABIC || direction == U_RIGHT_TO_LEFT_EMBEDDING || direction == U_RIGHT_TO_LEFT_OVERRIDE || direction == U_LEFT_TO_RIGHT_EMBEDDING || direction == U_LEFT_TO_RIGHT_OVERRIDE || direction == U_POP_DIRECTIONAL_FORMAT || direction == U_BOUNDARY_NEUTRAL) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextHasDirectionCharacter, reasons, includeReasons); + return false; - if (!font.glyphForCharacter(character)) - SET_REASON_AND_RETURN_IF_NEEDED(FlowFontIsMissingGlyph, reasons, includeReasons); + if (!fontData.glyphForCharacter(character)) + return false; } - return reasons; + return true; } -static AvoidanceReasonFlags canUseForText(const RenderText& textRenderer, const Font& font, IncludeReasons includeReasons) +static bool canUseForText(const RenderText& textRenderer, const SimpleFontData& fontData) { if (textRenderer.is8Bit()) - return canUseForText(textRenderer.characters8(), textRenderer.textLength(), font, includeReasons); - return canUseForText(textRenderer.characters16(), textRenderer.textLength(), font, includeReasons); + return canUseForText(textRenderer.characters8(), textRenderer.textLength(), fontData); + return canUseForText(textRenderer.characters16(), textRenderer.textLength(), fontData); } -static AvoidanceReasonFlags canUseForFontAndText(const RenderBlockFlow& flow, IncludeReasons includeReasons) +bool canUseFor(const RenderBlockFlow& flow) { - AvoidanceReasonFlags reasons = { }; - // We assume that all lines have metrics based purely on the primary font. - const auto& style = flow.style(); - auto& primaryFont = style.fontCascade().primaryFont(); - if (primaryFont.isLoading()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsMissingPrimaryFont, reasons, includeReasons); - if (primaryFont.isSVGFont()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasSVGFont, reasons, includeReasons); - - for (const auto& textRenderer : childrenOfType<RenderText>(flow)) { - if (textRenderer.isCombineText()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsCombineText, reasons, includeReasons); - if (textRenderer.isCounter()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderCounter, reasons, includeReasons); - if (textRenderer.isQuote()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsRenderQuote, reasons, includeReasons); - if (textRenderer.isTextFragment()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsTextFragment, reasons, includeReasons); - if (textRenderer.isSVGInlineText()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowTextIsSVGInlineText, reasons, includeReasons); - if (style.fontCascade().codePath(TextRun(textRenderer.text())) != FontCascade::Simple) - SET_REASON_AND_RETURN_IF_NEEDED(FlowFontIsNotSimple, reasons, includeReasons); - - auto textReasons = canUseForText(textRenderer, primaryFont, includeReasons); - if (textReasons != NoReason) - SET_REASON_AND_RETURN_IF_NEEDED(textReasons, reasons, includeReasons); +#if !PLATFORM(MAC) && !PLATFORM(GTK) && !PLATFORM(EFL) + // FIXME: Non-mac platforms are hitting ASSERT(run.charactersLength() >= run.length()) + // https://bugs.webkit.org/show_bug.cgi?id=123338 + return false; +#else + if (!flow.frame().settings().simpleLineLayoutEnabled()) + return false; + if (!flow.firstChild()) + return false; + // This currently covers <blockflow>#text</blockflow> case. + // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover. + if (flow.firstChild() != flow.lastChild()) + return false; + if (!flow.firstChild()->isText()) + return false; + if (!flow.isHorizontalWritingMode()) + return false; + if (flow.flowThreadState() != RenderObject::NotInsideFlowThread) + return false; + if (flow.hasOutline()) + return false; + if (flow.isRubyText() || flow.isRubyBase()) + return false; + if (flow.parent()->isDeprecatedFlexibleBox()) + return false; + // FIXME: Implementation of wrap=hard looks into lineboxes. + if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr)) + return false; + // FIXME: Placeholders do something strange. + if (flow.parent()->isTextControl() && toRenderTextControl(*flow.parent()).textFormControlElement().placeholderElement()) + return false; + // These tests only works during layout. Outside layout this function may give false positives. + if (flow.view().layoutState()) { +#if ENABLE(CSS_SHAPES) + if (flow.view().layoutState()->shapeInsideInfo()) + return false; +#endif + if (flow.view().layoutState()->m_columnInfo) + return false; } - return reasons; -} - -static AvoidanceReasonFlags canUseForStyle(const RenderStyle& style, IncludeReasons includeReasons) -{ - AvoidanceReasonFlags reasons = { }; - if (style.textOverflow()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons); - if ((style.textDecorationsInEffect() & TextDecorationUnderline) && style.textUnderlinePosition() == TextUnderlinePositionUnder) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedUnderlineDecoration, reasons, includeReasons); + const RenderStyle& style = flow.style(); + if (style.textDecorationsInEffect() != TextDecorationNone) + return false; if (style.textAlign() == JUSTIFY) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsJustifyAligned, reasons, includeReasons); + return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOverflowVisible, reasons, includeReasons); + return false; + if (!style.textIndent().isZero()) + return false; + if (!style.wordSpacing().isZero() || style.letterSpacing()) + return false; + if (style.textTransform() != TTNONE) + return false; if (!style.isLeftToRightDirection()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotLTR, reasons, includeReasons); + return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBoxContainProperty, reasons, includeReasons); + return false; if (style.writingMode() != TopToBottomWritingMode) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsNotTopToBottom, reasons, includeReasons); + return false; if (style.lineBreak() != LineBreakAuto) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineBreak, reasons, includeReasons); - if (style.unicodeBidi() != UBNormal) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonNormalUnicodeBiDi, reasons, includeReasons); - if (style.rtlOrdering() != LogicalOrder) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasRTLOrdering, reasons, includeReasons); - if (style.lineAlign() != LineAlignNone) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineAlignEdges, reasons, includeReasons); - if (style.lineSnap() != LineSnapNone) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasLineSnap, reasons, includeReasons); + return false; + if (style.wordBreak() != NormalWordBreak) + return false; + if (style.unicodeBidi() != UBNormal || style.rtlOrdering() != LogicalOrder) + return false; + if (style.lineAlign() != LineAlignNone || style.lineSnap() != LineSnapNone) + return false; if (style.hyphens() == HyphensAuto) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHypensAuto, reasons, includeReasons); + return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextEmphasisFillOrMark, reasons, includeReasons); + return false; if (style.textShadow()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextShadow, reasons, includeReasons); - if (style.hasPseudoStyle(FIRST_LINE)) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons); - if (style.hasPseudoStyle(FIRST_LETTER)) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLetter, reasons, includeReasons); + return false; +#if ENABLE(CSS_SHAPES) + if (style.resolvedShapeInside()) + return true; +#endif + if (style.textOverflow() || (flow.isAnonymousBlock() && flow.parent()->style().textOverflow())) + return false; + if (style.hasPseudoStyle(FIRST_LINE) || style.hasPseudoStyle(FIRST_LETTER)) + return false; if (style.hasTextCombine()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextCombine, reasons, includeReasons); + return false; if (style.backgroundClip() == TextFillBox) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextFillBox, reasons, includeReasons); + return false; if (style.borderFit() == BorderFitLines) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasBorderFitLines, reasons, includeReasons); - if (style.lineBreak() != LineBreakAuto) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonAutoLineBreak, reasons, includeReasons); -#if ENABLE(CSS_TRAILING_WORD) - if (style.trailingWord() != TrailingWord::Auto) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonAutoTrailingWord, reasons, includeReasons); + return false; + const RenderText& textRenderer = toRenderText(*flow.firstChild()); + if (flow.containsFloats()) { + // 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. + // It is enough to test the first line width only as currently all floats must be overhanging. + if (textRenderer.minLogicalWidth() > LineWidth(const_cast<RenderBlockFlow&>(flow), false, DoNotIndentText).availableWidth()) + return false; + } + if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() +#if ENABLE(SVG) + || textRenderer.isSVGInlineText() #endif - return reasons; -} + ) + return false; + if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) + return false; + if (style.font().isSVGFont()) + return false; -static AvoidanceReasonFlags canUseForWithReason(const RenderBlockFlow& flow, IncludeReasons includeReasons) -{ -#ifndef NDEBUG - static std::once_flag onceFlag; - std::call_once(onceFlag, [] { - registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutCoverage", printSimpleLineLayoutCoverage); - registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutReasons", printSimpleLineLayoutBlockList); - registerNotifyCallback("com.apple.WebKit.toggleSimpleLineLayout", toggleSimpleLineLayout); - }); + // We assume that all lines have metrics based purely on the primary font. + auto& primaryFontData = *style.font().primaryFont(); + if (primaryFontData.isLoading()) + return false; + if (!canUseForText(textRenderer, primaryFontData)) + return false; + + return true; #endif - AvoidanceReasonFlags reasons = { }; - if (!flow.frame().settings().simpleLineLayoutEnabled()) - SET_REASON_AND_RETURN_IF_NEEDED(FeatureIsDisabled, reasons, includeReasons); - if (!flow.parent()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoParent, reasons, includeReasons); - if (!flow.firstChild()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNoChild, reasons, includeReasons); - if (flow.flowThreadState() != RenderObject::NotInsideFlowThread) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsInsideRegion, reasons, includeReasons); - if (!flow.isHorizontalWritingMode()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasHorizonalWritingMode, reasons, includeReasons); - if (flow.hasOutline()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasOutline, reasons, includeReasons); - if (flow.isRubyText() || flow.isRubyBase()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsRuby, reasons, includeReasons); - // Printing does pagination without a flow thread. - if (flow.document().paginated()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsPaginated, reasons, includeReasons); - if (flow.firstLineBlock()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasPseudoFirstLine, reasons, includeReasons); - if (flow.isAnonymousBlock() && flow.parent()->style().textOverflow()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasTextOverflow, reasons, includeReasons); - if (flow.parent()->isDeprecatedFlexibleBox()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowIsDepricatedFlexBox, reasons, includeReasons); - // FIXME: Placeholders do something strange. - if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsPlaceholderElement, reasons, includeReasons); - // FIXME: Implementation of wrap=hard looks into lineboxes. - if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr)) - SET_REASON_AND_RETURN_IF_NEEDED(FlowParentIsTextAreaWithWrapping, reasons, includeReasons); - // This currently covers <blockflow>#text</blockflow>, <blockflow>#text<br></blockflow> and mutiple (sibling) RenderText cases. - // The <blockflow><inline>#text</inline></blockflow> case is also popular and should be relatively easy to cover. - for (const auto* child = flow.firstChild(); child;) { - if (child->selectionState() != RenderObject::SelectionNone) - SET_REASON_AND_RETURN_IF_NEEDED(FlowChildIsSelected, reasons, includeReasons); - if (is<RenderText>(*child)) { - child = child->nextSibling(); - continue; - } - if (is<RenderLineBreak>(child) && !downcast<RenderLineBreak>(*child).isWBR() && child->style().clear() == CNONE) { - child = child->nextSibling(); - continue; - } - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasNonSupportedChild, reasons, includeReasons); - break; +} + +struct Style { + Style(const RenderStyle& style) + : font(style.font()) + , textAlign(style.textAlign()) + , collapseWhitespace(style.collapseWhiteSpace()) + , preserveNewline(style.preserveNewline()) + , wrapLines(style.autoWrap()) + , breakWordOnOverflow(style.overflowWrap() == BreakOverflowWrap && (wrapLines || preserveNewline)) + , spaceWidth(font.width(TextRun(&space, 1))) + , tabWidth(collapseWhitespace ? 0 : style.tabSize()) + { } - auto styleReasons = canUseForStyle(flow.style(), includeReasons); - if (styleReasons != NoReason) - SET_REASON_AND_RETURN_IF_NEEDED(styleReasons, reasons, includeReasons); - // 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. - if (flow.containsFloats()) { - float minimumWidthNeeded = std::numeric_limits<float>::max(); - for (const auto& textRenderer : childrenOfType<RenderText>(flow)) { - minimumWidthNeeded = std::min(minimumWidthNeeded, textRenderer.minLogicalWidth()); + const Font& font; + ETextAlign textAlign; + bool collapseWhitespace; + bool preserveNewline; + bool wrapLines; + bool breakWordOnOverflow; + float spaceWidth; + unsigned tabWidth; +}; - for (auto& floatingObject : *flow.floatingObjectSet()) { - ASSERT(floatingObject); -#if ENABLE(CSS_SHAPES) - // if a float has a shape, we cannot tell if content will need to be shifted until after we lay it out, - // since the amount of space is not uniform for the height of the float. - if (floatingObject->renderer().shapeOutsideInfo()) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons); -#endif - float availableWidth = flow.availableLogicalWidthForLine(floatingObject->y(), DoNotIndentText); - if (availableWidth < minimumWidthNeeded) - SET_REASON_AND_RETURN_IF_NEEDED(FlowHasUnsupportedFloat, reasons, includeReasons); - } - } +static inline bool isWhitespace(UChar character, bool preserveNewline) +{ + return character == ' ' || character == '\t' || (!preserveNewline && character == '\n'); +} + +template <typename CharacterType> +static inline unsigned skipWhitespaces(const CharacterType* text, unsigned offset, unsigned length, bool preserveNewline) +{ + for (; offset < length; ++offset) { + if (!isWhitespace(text[offset], preserveNewline)) + return offset; } - auto fontAndTextReasons = canUseForFontAndText(flow, includeReasons); - if (fontAndTextReasons != NoReason) - SET_REASON_AND_RETURN_IF_NEEDED(fontAndTextReasons, reasons, includeReasons); - return reasons; + return length; } -bool canUseFor(const RenderBlockFlow& flow) +template <typename CharacterType> +static float textWidth(const RenderText& renderText, const CharacterType* text, unsigned textLength, unsigned from, unsigned to, float xPosition, const Style& style) { - return canUseForWithReason(flow, IncludeReasons::First) == NoReason; + if (style.font.isFixedPitch() || (!from && to == textLength)) + return renderText.width(from, to - from, style.font, xPosition, nullptr, nullptr); + + TextRun run(text + from, to - from); + run.setXPos(xPosition); + run.setCharactersLength(textLength - from); + run.setTabSize(!!style.tabWidth, style.tabWidth); + + ASSERT(run.charactersLength() >= run.length()); + + return style.font.width(run); } -static float computeLineLeft(ETextAlign textAlign, float availableWidth, float committedWidth, float logicalLeftOffset) +template <typename CharacterType> +static float measureWord(unsigned start, unsigned end, float lineWidth, const Style& style, const CharacterType* text, unsigned textLength, const RenderText& textRenderer) { - float remainingWidth = availableWidth - committedWidth; - float left = logicalLeftOffset; - switch (textAlign) { - case LEFT: - case WEBKIT_LEFT: - case TASTART: - return left; - case RIGHT: - case WEBKIT_RIGHT: - case TAEND: - return left + std::max<float>(remainingWidth, 0); - case CENTER: - case WEBKIT_CENTER: - return left + std::max<float>(remainingWidth / 2, 0); - case JUSTIFY: - ASSERT_NOT_REACHED(); - break; - } - ASSERT_NOT_REACHED(); - return 0; + if (text[start] == ' ' && end == start + 1) + return style.spaceWidth; + + bool measureWithEndSpace = style.collapseWhitespace && end < textLength && text[end] == ' '; + if (measureWithEndSpace) + ++end; + float width = textWidth(textRenderer, text, textLength, start, end, lineWidth, style); + + return measureWithEndSpace ? width - style.spaceWidth : width; } -static void revertRuns(Layout::RunVector& runs, unsigned length, float width) +template <typename CharacterType> +Vector<Run, 4> createLineRuns(unsigned lineStart, LineWidth& lineWidth, LazyLineBreakIterator& lineBreakIterator, const Style& style, const CharacterType* text, unsigned textLength, const RenderText& textRenderer) { - while (length) { - ASSERT(runs.size()); - Run& lastRun = runs.last(); - unsigned lastRunLength = lastRun.end - lastRun.start; - if (lastRunLength > length) { - lastRun.logicalRight -= width; - lastRun.end -= length; + Vector<Run, 4> lineRuns; + lineRuns.uncheckedAppend(Run(lineStart, 0)); + + unsigned wordEnd = lineStart; + while (wordEnd < textLength) { + ASSERT(!style.collapseWhitespace || !isWhitespace(text[wordEnd], style.preserveNewline)); + + unsigned wordStart = wordEnd; + + if (style.preserveNewline && text[wordStart] == '\n') { + ++wordEnd; + // FIXME: This creates a dedicated run for newline. This is wasteful and unnecessary but it keeps test results unchanged. + if (wordStart > lineStart) + lineRuns.append(Run(wordStart, lineRuns.last().right)); + lineRuns.last().right = lineRuns.last().left; + lineRuns.last().end = wordEnd; break; } - length -= lastRunLength; - width -= (lastRun.logicalRight - lastRun.logicalLeft); - runs.removeLast(); - } -} -class LineState { -public: - void setAvailableWidth(float width) { m_availableWidth = width; } - void setCollapedWhitespaceWidth(float width) { m_collapsedWhitespaceWidth = width; } - void setLogicalLeftOffset(float offset) { m_logicalLeftOffset = offset; } - void setOverflowedFragment(const TextFragmentIterator::TextFragment& fragment) { m_overflowedFragment = fragment; } + if (!style.collapseWhitespace && isWhitespace(text[wordStart], style.preserveNewline)) + wordEnd = wordStart + 1; + else + wordEnd = nextBreakablePosition<CharacterType, false>(lineBreakIterator, text, textLength, wordStart + 1); - float availableWidth() const { return m_availableWidth; } - float logicalLeftOffset() const { return m_logicalLeftOffset; } - const TextFragmentIterator::TextFragment& overflowedFragment() const { return m_overflowedFragment; } - bool hasTrailingWhitespace() const { return m_trailingWhitespaceLength; } - TextFragmentIterator::TextFragment lastFragment() const { return m_fragments.last(); } - bool isWhitespaceOnly() const { return m_trailingWhitespaceWidth && m_runsWidth == m_trailingWhitespaceWidth; } - bool fits(float extra) const { return m_availableWidth >= m_runsWidth + extra; } - bool firstCharacterFits() const { return m_firstCharacterFits; } - float width() const { return m_runsWidth; } - bool isEmpty() const - { - if (!m_fragments.size()) - return true; - if (!m_lastCompleteFragment.isEmpty()) - return false; - return m_fragments.last().overlapsToNextRenderer(); - } + bool wordIsPrecededByWhitespace = style.collapseWhitespace && wordStart > lineStart && isWhitespace(text[wordStart - 1], style.preserveNewline); + if (wordIsPrecededByWhitespace) + --wordStart; - void appendFragmentAndCreateRunIfNeeded(const TextFragmentIterator::TextFragment& fragment, Layout::RunVector& runs) - { - // Adjust end position while collapsing. - unsigned endPosition = fragment.isCollapsed() ? fragment.start() + 1 : fragment.end(); - // New line needs new run. - if (!m_runsWidth) - runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false)); - else { - const auto& lastFragment = m_fragments.last(); - // Advance last completed fragment when the previous fragment is all set (including multiple parts across renderers) - if ((lastFragment.type() != fragment.type()) || !lastFragment.overlapsToNextRenderer()) - m_lastCompleteFragment = lastFragment; - // Collapse neighbouring whitespace, if they are across multiple renderers and are not collapsed yet. - if (lastFragment.isCollapsible() && fragment.isCollapsible()) { - ASSERT(lastFragment.isLastInRenderer()); - if (!lastFragment.isCollapsed()) { - // Line width needs to be reset so that now it takes collapsing into consideration. - m_runsWidth -= (lastFragment.width() - m_collapsedWhitespaceWidth); + float wordWidth = measureWord(wordStart, wordEnd, lineWidth.committedWidth(), style, text, textLength, textRenderer); + + lineWidth.addUncommittedWidth(wordWidth); + + if (style.wrapLines) { + // Move to the next line if the current one is full and we have something on it. + if (!lineWidth.fitsOnLine() && lineWidth.committedWidth()) + break; + + // This is for white-space: pre-wrap which requires special handling for end line whitespace. + if (!style.collapseWhitespace && lineWidth.fitsOnLine() && wordEnd < textLength && isWhitespace(text[wordEnd], style.preserveNewline)) { + // Look ahead to see if the next whitespace would fit. + float whitespaceWidth = textWidth(textRenderer, text, textLength, wordEnd, wordEnd + 1, lineWidth.committedWidth(), style); + if (!lineWidth.fitsOnLineIncludingExtraWidth(whitespaceWidth)) { + // If not eat away the rest of the whitespace on the line. + unsigned whitespaceEnd = skipWhitespaces(text, wordEnd, textLength, style.preserveNewline); + // Include newline to this run too. + if (whitespaceEnd < textLength && text[whitespaceEnd] == '\n') + ++whitespaceEnd; + lineRuns.last().end = whitespaceEnd; + lineRuns.last().right = lineWidth.availableWidth(); + break; } - // This fragment is collapsed completely. No run is needed. - return; - } - if (lastFragment.isLastInRenderer() || lastFragment.isCollapsed()) - runs.append(Run(fragment.start(), endPosition, m_runsWidth, m_runsWidth + fragment.width(), false)); - else { - Run& lastRun = runs.last(); - lastRun.end = endPosition; - lastRun.logicalRight += fragment.width(); } } - m_fragments.append(fragment); - m_runsWidth += fragment.width(); - if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) { - m_trailingWhitespaceLength += endPosition - fragment.start(); - m_trailingWhitespaceWidth += fragment.width(); - } else { - m_trailingWhitespaceLength = 0; - m_trailingWhitespaceWidth = 0; + if (wordStart > lineRuns.last().end) { + // There were more than one consecutive whitespace. + ASSERT(wordIsPrecededByWhitespace); + // Include space to the end of the previous run. + lineRuns.last().end++; + lineRuns.last().right += style.spaceWidth; + // Start a new run on the same line. + lineRuns.append(Run(wordStart + 1, lineRuns.last().right)); } - if (!m_firstCharacterFits) - m_firstCharacterFits = fragment.start() + 1 > endPosition || m_runsWidth <= m_availableWidth; - } - - TextFragmentIterator::TextFragment revertToLastCompleteFragment(Layout::RunVector& runs) - { - ASSERT(m_fragments.size()); - unsigned revertLength = 0; - float revertWidth = 0; - while (m_fragments.size()) { - const auto& current = m_fragments.last(); - if (current == m_lastCompleteFragment) - break; - revertLength += current.end() - current.start(); - revertWidth += current.width(); - m_fragments.removeLast(); + if (!lineWidth.fitsOnLine() && style.breakWordOnOverflow) { + // Backtrack and start measuring character-by-character. + lineWidth.addUncommittedWidth(-lineWidth.uncommittedWidth()); + unsigned splitEnd = wordStart; + for (; splitEnd < wordEnd; ++splitEnd) { + float charWidth = textWidth(textRenderer, text, textLength, splitEnd, splitEnd + 1, 0, style); + lineWidth.addUncommittedWidth(charWidth); + if (!lineWidth.fitsOnLine() && splitEnd > lineStart) + break; + lineWidth.commit(); + } + lineRuns.last().end = splitEnd; + lineRuns.last().right = lineWidth.committedWidth(); + // To match line boxes, set single-space-only line width to zero. + if (text[lineRuns.last().start] == ' ' && lineRuns.last().start + 1 == lineRuns.last().end) + lineRuns.last().right = lineRuns.last().left; + break; } - m_runsWidth -= revertWidth; - if (revertLength) - revertRuns(runs, revertLength, revertWidth); - return m_lastCompleteFragment; - } - void removeTrailingWhitespace(Layout::RunVector& runs) - { - if (!m_trailingWhitespaceLength) - return; - revertRuns(runs, m_trailingWhitespaceLength, m_trailingWhitespaceWidth); - m_runsWidth -= m_trailingWhitespaceWidth; - ASSERT(m_fragments.last().type() == TextFragmentIterator::TextFragment::Whitespace); - while (m_fragments.size()) { - const auto& current = m_fragments.last(); - if (current.type() != TextFragmentIterator::TextFragment::Whitespace) - break; -#if !ASSERT_DISABLED - m_trailingWhitespaceLength -= (current.isCollapsed() ? 1 : current.end() - current.start()); - m_trailingWhitespaceWidth -= current.width(); -#endif - m_fragments.removeLast(); - } -#if !ASSERT_DISABLED - ASSERT(!m_trailingWhitespaceLength); - ASSERT(!m_trailingWhitespaceWidth); -#endif - m_trailingWhitespaceLength = 0; - m_trailingWhitespaceWidth = 0; - } + lineWidth.commit(); -private: - float m_availableWidth { 0 }; - float m_logicalLeftOffset { 0 }; - TextFragmentIterator::TextFragment m_overflowedFragment; - float m_runsWidth { 0 }; - TextFragmentIterator::TextFragment m_lastCompleteFragment; - float m_trailingWhitespaceWidth { 0 }; // Use this to remove trailing whitespace without re-mesuring the text. - unsigned m_trailingWhitespaceLength { 0 }; - float m_collapsedWhitespaceWidth { 0 }; - // Having one character on the line does not necessarily mean it actually fits. - // First character of the first fragment might be forced on to the current line even if it does not fit. - bool m_firstCharacterFits { false }; - Vector<TextFragmentIterator::TextFragment> m_fragments; -}; + lineRuns.last().right = lineWidth.committedWidth(); + lineRuns.last().end = wordEnd; -class FragmentForwardIterator : public std::iterator<std::forward_iterator_tag, unsigned> { -public: - FragmentForwardIterator(unsigned fragmentIndex) - : m_fragmentIndex(fragmentIndex) - { - } + if (style.collapseWhitespace) + wordEnd = skipWhitespaces(text, wordEnd, textLength, style.preserveNewline); - FragmentForwardIterator& operator++() - { - ++m_fragmentIndex; - return *this; + if (!lineWidth.fitsOnLine() && style.wrapLines) { + // The first run on the line overflows. + ASSERT(lineRuns.size() == 1); + break; + } } + return lineRuns; +} - bool operator!=(const FragmentForwardIterator& other) const { return m_fragmentIndex != other.m_fragmentIndex; } - unsigned operator*() const { return m_fragmentIndex; } - -private: - unsigned m_fragmentIndex { 0 }; -}; - -static FragmentForwardIterator begin(const TextFragmentIterator::TextFragment& fragment) { return FragmentForwardIterator(fragment.start()); } -static FragmentForwardIterator end(const TextFragmentIterator::TextFragment& fragment) { return FragmentForwardIterator(fragment.end()); } - -static bool preWrap(const TextFragmentIterator::Style& style) +static float computeLineLeft(ETextAlign textAlign, const LineWidth& lineWidth) { - return style.wrapLines && !style.collapseWhitespace; + float remainingWidth = lineWidth.availableWidth() - lineWidth.committedWidth(); + float left = lineWidth.logicalLeftOffset(); + switch (textAlign) { + case LEFT: + case WEBKIT_LEFT: + case TASTART: + return left; + case RIGHT: + case WEBKIT_RIGHT: + case TAEND: + return left + std::max<float>(remainingWidth, 0); + case CENTER: + case WEBKIT_CENTER: + return left + std::max<float>(remainingWidth / 2, 0); + case JUSTIFY: + break; + } + ASSERT_NOT_REACHED(); + return 0; } - -static void removeTrailingWhitespace(LineState& lineState, Layout::RunVector& runs, const TextFragmentIterator& textFragmentIterator) -{ - if (!lineState.hasTrailingWhitespace()) - return; - // Remove collapsed whitespace, or non-collapsed pre-wrap whitespace, unless it's the only content on the line -so removing the whitesapce would produce an empty line. - const auto& style = textFragmentIterator.style(); - bool collapseWhitespace = style.collapseWhitespace | preWrap(style); - if (!collapseWhitespace) - return; - - if (preWrap(style) && lineState.isWhitespaceOnly()) +static void adjustRunOffsets(Vector<Run, 4>& lineRuns, float adjustment) +{ + if (!adjustment) return; - - lineState.removeTrailingWhitespace(runs); + for (unsigned i = 0; i < lineRuns.size(); ++i) { + lineRuns[i].left += adjustment; + lineRuns[i].right += adjustment; + } } -static void updateLineConstrains(const RenderBlockFlow& flow, LineState& line, bool isFirstLine) +template <typename CharacterType> +void createTextRuns(Layout::RunVector& runs, unsigned& lineCount, RenderBlockFlow& flow, RenderText& textRenderer) { - bool shouldApplyTextIndent = !flow.isAnonymous() || flow.parent()->firstChild() == &flow; - LayoutUnit height = flow.logicalHeight(); - LayoutUnit logicalHeight = flow.minLineHeightForReplacedRenderer(false, 0); - float logicalRightOffset = flow.logicalRightOffsetForLine(height, DoNotIndentText, logicalHeight); - line.setLogicalLeftOffset(flow.logicalLeftOffsetForLine(height, DoNotIndentText, logicalHeight) + - (shouldApplyTextIndent && isFirstLine ? flow.textIndentOffset() : LayoutUnit(0))); - line.setAvailableWidth(std::max<float>(0, logicalRightOffset - line.logicalLeftOffset())); -} + const Style style(flow.style()); -static TextFragmentIterator::TextFragment splitFragmentToFitLine(TextFragmentIterator::TextFragment& fragmentToSplit, float availableWidth, bool keepAtLeastOneCharacter, const TextFragmentIterator& textFragmentIterator) -{ - // FIXME: add surrogate pair support. - unsigned start = fragmentToSplit.start(); - auto it = std::upper_bound(begin(fragmentToSplit), end(fragmentToSplit), availableWidth, [&textFragmentIterator, start](float availableWidth, unsigned index) { - // FIXME: use the actual left position of the line (instead of 0) to calculated width. It might give false width for tab characters. - return availableWidth < textFragmentIterator.textWidth(start, index + 1, 0); - }); - unsigned splitPosition = (*it); - if (keepAtLeastOneCharacter && splitPosition == fragmentToSplit.start()) - ++splitPosition; - return fragmentToSplit.split(splitPosition, textFragmentIterator); -} + const CharacterType* text = textRenderer.text()->getCharacters<CharacterType>(); + const unsigned textLength = textRenderer.textLength(); -enum PreWrapLineBreakRule { Preserve, Ignore }; + LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore(); + LayoutUnit lineHeight = lineHeightFromFlow(flow); -static TextFragmentIterator::TextFragment consumeLineBreakIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator, LineState& line, Layout::RunVector& runs, - PreWrapLineBreakRule preWrapLineBreakRule = PreWrapLineBreakRule::Preserve) -{ - if (!fragment.isLineBreak()) - return fragment; + LazyLineBreakIterator lineBreakIterator(textRenderer.text(), flow.style().locale()); - if (preWrap(textFragmentIterator.style()) && preWrapLineBreakRule != PreWrapLineBreakRule::Ignore) - return fragment; + unsigned lineEnd = 0; + while (lineEnd < textLength) { + if (style.collapseWhitespace) + lineEnd = skipWhitespaces(text, lineEnd, textLength, style.preserveNewline); - // <br> always produces a run. (required by testing output) - if (fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak) - line.appendFragmentAndCreateRunIfNeeded(fragment, runs); - return textFragmentIterator.nextTextFragment(); -} + unsigned lineStart = lineEnd; -static TextFragmentIterator::TextFragment skipWhitespaceIfNeeded(const TextFragmentIterator::TextFragment& fragment, TextFragmentIterator& textFragmentIterator) -{ - if (!textFragmentIterator.style().collapseWhitespace) - return fragment; + // LineWidth reads the current y position from the flow so keep it updated. + flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore); + LineWidth lineWidth(flow, false, DoNotIndentText); - TextFragmentIterator::TextFragment firstNonWhitespaceFragment = fragment; - while (firstNonWhitespaceFragment.type() == TextFragmentIterator::TextFragment::Whitespace) - firstNonWhitespaceFragment = textFragmentIterator.nextTextFragment(); - return firstNonWhitespaceFragment; -} + auto lineRuns = createLineRuns(lineStart, lineWidth, lineBreakIterator, style, text, textLength, textRenderer); -static TextFragmentIterator::TextFragment firstFragment(TextFragmentIterator& textFragmentIterator, LineState& currentLine, const LineState& previousLine, Layout::RunVector& runs) -{ - // Handle overflowed fragment from previous line. - TextFragmentIterator::TextFragment firstFragment(previousLine.overflowedFragment()); + lineEnd = lineRuns.last().end; + if (lineStart == lineEnd) + continue; - if (firstFragment.isEmpty()) - firstFragment = textFragmentIterator.nextTextFragment(); - else if (firstFragment.type() == TextFragmentIterator::TextFragment::Whitespace && preWrap(textFragmentIterator.style()) && previousLine.firstCharacterFits()) { - // Special overflow pre-wrap whitespace handling: skip the overflowed whitespace (even when style says not-collapsible) if we managed to fit at least one character on the previous line. - firstFragment = textFragmentIterator.nextTextFragment(); - // If skipping the whitespace puts us on a newline, skip the newline too as we already wrapped the line. - firstFragment = consumeLineBreakIfNeeded(firstFragment, textFragmentIterator, currentLine, runs, PreWrapLineBreakRule::Ignore); - } - return skipWhitespaceIfNeeded(firstFragment, textFragmentIterator); -} + lineRuns.last().isEndOfLine = true; -static void forceFragmentToLine(LineState& line, TextFragmentIterator& textFragmentIterator, Layout::RunVector& runs, const TextFragmentIterator::TextFragment& fragment) -{ - line.appendFragmentAndCreateRunIfNeeded(fragment, runs); - // Check if there are more fragments to add to the current line. - auto nextFragment = textFragmentIterator.nextTextFragment(); - if (fragment.overlapsToNextRenderer()) { - while (true) { - if (nextFragment.type() != fragment.type()) - break; - line.appendFragmentAndCreateRunIfNeeded(nextFragment, runs); - // Does it overlap to the next segment? - if (!nextFragment.overlapsToNextRenderer()) - return; - nextFragment = textFragmentIterator.nextTextFragment(); - } - } - // 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. - nextFragment = skipWhitespaceIfNeeded(nextFragment, textFragmentIterator); - nextFragment = consumeLineBreakIfNeeded(nextFragment, textFragmentIterator, line, runs); - line.setOverflowedFragment(nextFragment); -} + float lineLeft = computeLineLeft(style.textAlign, lineWidth); + adjustRunOffsets(lineRuns, lineLeft); -static bool createLineRuns(LineState& line, const LineState& previousLine, Layout::RunVector& runs, TextFragmentIterator& textFragmentIterator) -{ - const auto& style = textFragmentIterator.style(); - line.setCollapedWhitespaceWidth(style.spaceWidth + style.wordSpacing); - bool lineCanBeWrapped = style.wrapLines || style.breakFirstWordOnOverflow || style.breakAnyWordOnOverflow; - auto fragment = firstFragment(textFragmentIterator, line, previousLine, runs); - while (fragment.type() != TextFragmentIterator::TextFragment::ContentEnd) { - // Hard linebreak. - if (fragment.isLineBreak()) { - // 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.) - if (line.isEmpty() || fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak) { - if (style.textAlign == RIGHT || style.textAlign == WEBKIT_RIGHT) - line.removeTrailingWhitespace(runs); - line.appendFragmentAndCreateRunIfNeeded(fragment, runs); - } - break; - } - if (lineCanBeWrapped && !line.fits(fragment.width())) { - // Overflow wrapping behaviour: - // 1. Whitesapce collapse on: whitespace is skipped. Jump to next line. - // 2. Whitespace collapse off: whitespace is wrapped. - // 3. First, non-whitespace fragment is either wrapped or kept on the line. (depends on overflow-wrap) - // 5. Non-whitespace fragment when there's already another fragment on the line either gets wrapped (word-break: break-all) - // or gets pushed to the next line. - bool emptyLine = line.isEmpty(); - // Whitespace fragment. - if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) { - if (!style.collapseWhitespace) { - // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line. - line.setOverflowedFragment(splitFragmentToFitLine(fragment, line.availableWidth() - line.width(), emptyLine, textFragmentIterator)); - line.appendFragmentAndCreateRunIfNeeded(fragment, runs); - } - // When whitespace collapse is on, whitespace that doesn't fit is simply skipped. - break; - } - // Non-whitespace fragment. (!style.wrapLines: bug138102(preserve existing behavior) - if (((emptyLine && style.breakFirstWordOnOverflow) || style.breakAnyWordOnOverflow) || !style.wrapLines) { - // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line. - line.setOverflowedFragment(splitFragmentToFitLine(fragment, line.availableWidth() - line.width(), emptyLine, textFragmentIterator)); - line.appendFragmentAndCreateRunIfNeeded(fragment, runs); - break; - } - // Non-breakable non-whitespace first fragment. Add it to the current line. -it overflows though. - ASSERT(fragment.type() == TextFragmentIterator::TextFragment::NonWhitespace); - if (emptyLine) { - forceFragmentToLine(line, textFragmentIterator, runs, fragment); - break; - } - // Non-breakable non-whitespace fragment when there's already content on the line. Push it to the next line. - if (line.lastFragment().overlapsToNextRenderer()) { - // Check if this fragment is a continuation of a previous segment. In such cases, we need to remove them all. - const auto& lastCompleteFragment = line.revertToLastCompleteFragment(runs); - textFragmentIterator.revertToEndOfFragment(lastCompleteFragment); - break; - } - line.setOverflowedFragment(fragment); - break; - } - line.appendFragmentAndCreateRunIfNeeded(fragment, runs); - // Find the next text fragment. - fragment = textFragmentIterator.nextTextFragment(line.width()); - } - return (fragment.type() == TextFragmentIterator::TextFragment::ContentEnd && line.overflowedFragment().isEmpty()) || line.overflowedFragment().type() == TextFragmentIterator::TextFragment::ContentEnd; -} + for (unsigned i = 0; i < lineRuns.size(); ++i) + runs.append(lineRuns[i]); -static void closeLineEndingAndAdjustRuns(LineState& line, Layout::RunVector& runs, unsigned previousRunCount, unsigned& lineCount, const TextFragmentIterator& textFragmentIterator) -{ - if (previousRunCount == runs.size()) - return; - ASSERT(runs.size()); - removeTrailingWhitespace(line, runs, textFragmentIterator); - if (!runs.size()) - return; - // Adjust runs' position by taking line's alignment into account. - if (float lineLogicalLeft = computeLineLeft(textFragmentIterator.style().textAlign, line.availableWidth(), line.width(), line.logicalLeftOffset())) { - for (unsigned i = previousRunCount; i < runs.size(); ++i) { - runs[i].logicalLeft += lineLogicalLeft; - runs[i].logicalRight += lineLogicalLeft; - } + ++lineCount; } - runs.last().isEndOfLine = true; - ++lineCount; -} - -static void createTextRuns(Layout::RunVector& runs, RenderBlockFlow& flow, unsigned& lineCount) -{ - LayoutUnit borderAndPaddingBefore = flow.borderAndPaddingBefore(); - LayoutUnit lineHeight = lineHeightFromFlow(flow); - LineState line; - bool isEndOfContent = false; - TextFragmentIterator textFragmentIterator = TextFragmentIterator(flow); - do { - flow.setLogicalHeight(lineHeight * lineCount + borderAndPaddingBefore); - LineState previousLine = line; - unsigned previousRunCount = runs.size(); - line = LineState(); - updateLineConstrains(flow, line, !lineCount); - isEndOfContent = createLineRuns(line, previousLine, runs, textFragmentIterator); - closeLineEndingAndAdjustRuns(line, runs, previousRunCount, lineCount, textFragmentIterator); - } while (!isEndOfContent); } std::unique_ptr<Layout> create(RenderBlockFlow& flow) { - unsigned lineCount = 0; Layout::RunVector runs; + unsigned lineCount = 0; + + RenderText& textRenderer = toRenderText(*flow.firstChild()); + ASSERT(!textRenderer.firstTextBox()); + + if (textRenderer.is8Bit()) + createTextRuns<LChar>(runs, lineCount, flow, textRenderer); + else + createTextRuns<UChar>(runs, lineCount, flow, textRenderer); + + textRenderer.clearNeedsLayout(); - createTextRuns(runs, flow, lineCount); - for (auto& renderer : childrenOfType<RenderObject>(flow)) { - ASSERT(is<RenderText>(renderer) || is<RenderLineBreak>(renderer)); - renderer.clearNeedsLayout(); - } return Layout::create(runs, lineCount); } @@ -782,304 +483,5 @@ Layout::Layout(const RunVector& runVector, unsigned lineCount) memcpy(m_runs, runVector.data(), m_runCount * sizeof(Run)); } -#ifndef NDEBUG -static void printReason(AvoidanceReason reason, TextStream& stream) -{ - switch (reason) { - case FlowIsInsideRegion: - stream << "flow is inside region"; - break; - case FlowHasHorizonalWritingMode: - stream << "horizontal writing mode"; - break; - case FlowHasOutline: - stream << "outline"; - break; - case FlowIsRuby: - stream << "ruby"; - break; - case FlowIsPaginated: - stream << "paginated"; - break; - case FlowHasTextOverflow: - stream << "text-overflow"; - break; - case FlowIsDepricatedFlexBox: - stream << "depricatedFlexBox"; - break; - case FlowParentIsPlaceholderElement: - stream << "placeholder element"; - break; - case FlowParentIsTextAreaWithWrapping: - stream << "wrapping textarea"; - break; - case FlowHasNonSupportedChild: - stream << "nested renderers"; - break; - case FlowHasUnsupportedFloat: - stream << "complicated float"; - break; - case FlowHasUnsupportedUnderlineDecoration: - stream << "text-underline-position: under"; - break; - case FlowIsJustifyAligned: - stream << "text-align: justify"; - break; - case FlowHasOverflowVisible: - stream << "overflow: visible"; - break; - case FlowIsNotLTR: - stream << "dir is not LTR"; - break; - case FlowHasLineBoxContainProperty: - stream << "line-box-contain property"; - break; - case FlowIsNotTopToBottom: - stream << "non top-to-bottom flow"; - break; - case FlowHasLineBreak: - stream << "line-break property"; - break; - case FlowHasNonNormalUnicodeBiDi: - stream << "non-normal Unicode bidi"; - break; - case FlowHasRTLOrdering: - stream << "-webkit-rtl-ordering"; - break; - case FlowHasLineAlignEdges: - stream << "-webkit-line-align edges"; - break; - case FlowHasLineSnap: - stream << "-webkit-line-snap property"; - break; - case FlowHasHypensAuto: - stream << "hyphen: auto"; - break; - case FlowHasTextEmphasisFillOrMark: - stream << "text-emphasis (fill/mark)"; - break; - case FlowHasPseudoFirstLine: - stream << "first-line"; - break; - case FlowHasPseudoFirstLetter: - stream << "first-letter"; - break; - case FlowHasTextCombine: - stream << "text combine"; - break; - case FlowHasTextFillBox: - stream << "background-color (text-fill)"; - break; - case FlowHasBorderFitLines: - stream << "-webkit-border-fit"; - break; - case FlowHasNonAutoLineBreak: - stream << "line-break is not auto"; - break; - case FlowHasNonAutoTrailingWord: - stream << "-apple-trailing-word is not auto"; - break; - case FlowHasSVGFont: - stream << "SVG font"; - break; - case FlowTextHasNoBreakSpace: - stream << "No-break-space character"; - break; - case FlowTextHasSoftHyphen: - stream << "soft hyphen character"; - break; - case FlowTextHasDirectionCharacter: - stream << "direction character"; - break; - case FlowIsMissingPrimaryFont: - stream << "missing primary font"; - break; - case FlowFontIsMissingGlyph: - stream << "missing glyph"; - break; - case FlowTextIsCombineText: - stream << "text is combine"; - break; - case FlowTextIsRenderCounter: - stream << "unsupported RenderCounter"; - break; - case FlowTextIsRenderQuote: - stream << "unsupported RenderQuote"; - break; - case FlowTextIsTextFragment: - stream << "unsupported TextFragment"; - break; - case FlowTextIsSVGInlineText: - stream << "unsupported SVGInlineText"; - break; - case FlowFontIsNotSimple: - stream << "complext font"; - break; - case FlowHasTextShadow: - stream << "text-shadow"; - break; - case FlowChildIsSelected: - stream << "selected content"; - break; - case FlowTextIsEmpty: - case FlowHasNoChild: - case FlowHasNoParent: - case FeatureIsDisabled: - default: - break; - } -} - -static void printReasons(AvoidanceReasonFlags reasons, TextStream& stream) -{ - bool first = true; - for (auto reasonItem = EndOfReasons >> 1; reasonItem != NoReason; reasonItem >>= 1) { - if (!(reasons & reasonItem)) - continue; - stream << (first ? " " : ", "); - first = false; - printReason(reasonItem, stream); - } -} - -static void printTextForSubtree(const RenderObject& renderer, unsigned& charactersLeft, TextStream& stream) -{ - if (!charactersLeft) - return; - if (is<RenderText>(renderer)) { - String text = downcast<RenderText>(renderer).text(); - text = text.stripWhiteSpace(); - unsigned len = std::min(charactersLeft, text.length()); - stream << text.left(len); - charactersLeft -= len; - return; - } - if (!is<RenderElement>(renderer)) - return; - for (const auto* child = downcast<RenderElement>(renderer).firstChild(); child; child = child->nextSibling()) - printTextForSubtree(*child, charactersLeft, stream); -} - -static unsigned textLengthForSubtree(const RenderObject& renderer) -{ - if (is<RenderText>(renderer)) - return downcast<RenderText>(renderer).textLength(); - if (!is<RenderElement>(renderer)) - return 0; - unsigned textLength = 0; - for (const auto* child = downcast<RenderElement>(renderer).firstChild(); child; child = child->nextSibling()) - textLength += textLengthForSubtree(*child); - return textLength; -} - -static void collectNonEmptyLeafRenderBlockFlows(const RenderObject& renderer, HashSet<const RenderBlockFlow*>& leafRenderers) -{ - if (is<RenderText>(renderer)) { - if (!downcast<RenderText>(renderer).textLength()) - return; - // Find RenderBlockFlow ancestor. - for (const auto* current = renderer.parent(); current; current = current->parent()) { - if (!is<RenderBlockFlow>(current)) - continue; - leafRenderers.add(downcast<RenderBlockFlow>(current)); - break; - } - return; - } - if (!is<RenderElement>(renderer)) - return; - for (const auto* child = downcast<RenderElement>(renderer).firstChild(); child; child = child->nextSibling()) - collectNonEmptyLeafRenderBlockFlows(*child, leafRenderers); -} - -static void collectNonEmptyLeafRenderBlockFlowsForCurrentPage(HashSet<const RenderBlockFlow*>& leafRenderers) -{ - for (const auto* document : Document::allDocuments()) { - if (!document->renderView() || document->inPageCache()) - continue; - if (!document->isHTMLDocument() && !document->isXHTMLDocument()) - continue; - collectNonEmptyLeafRenderBlockFlows(*document->renderView(), leafRenderers); - } -} - -void toggleSimpleLineLayout() -{ - for (const auto* document : Document::allDocuments()) { - auto* settings = document->settings(); - if (!settings) - continue; - settings->setSimpleLineLayoutEnabled(!settings->simpleLineLayoutEnabled()); - } -} - -void printSimpleLineLayoutBlockList() -{ - HashSet<const RenderBlockFlow*> leafRenderers; - collectNonEmptyLeafRenderBlockFlowsForCurrentPage(leafRenderers); - if (!leafRenderers.size()) { - WTFLogAlways("No text found in this document\n"); - return; - } - TextStream stream; - stream << "---------------------------------------------------\n"; - for (const auto* flow : leafRenderers) { - auto reason = canUseForWithReason(*flow, IncludeReasons::All); - if (reason == NoReason) - continue; - unsigned printedLength = 30; - stream << "\""; - printTextForSubtree(*flow, printedLength, stream); - for (;printedLength > 0; --printedLength) - stream << " "; - stream << "\"(" << textLengthForSubtree(*flow) << "):"; - printReasons(reason, stream); - stream << "\n"; - } - stream << "---------------------------------------------------\n"; - WTFLogAlways("%s", stream.release().utf8().data()); -} - -void printSimpleLineLayoutCoverage() -{ - HashSet<const RenderBlockFlow*> leafRenderers; - collectNonEmptyLeafRenderBlockFlowsForCurrentPage(leafRenderers); - if (!leafRenderers.size()) { - WTFLogAlways("No text found in this document\n"); - return; - } - TextStream stream; - HashMap<AvoidanceReason, unsigned> flowStatistics; - unsigned textLength = 0; - unsigned unsupportedTextLength = 0; - unsigned numberOfUnsupportedLeafBlocks = 0; - for (const auto* flow : leafRenderers) { - auto flowLength = textLengthForSubtree(*flow); - textLength += flowLength; - auto reasons = canUseForWithReason(*flow, IncludeReasons::All); - if (reasons == NoReason) - continue; - ++numberOfUnsupportedLeafBlocks; - unsupportedTextLength += flowLength; - for (auto reasonItem = EndOfReasons >> 1; reasonItem != NoReason; reasonItem >>= 1) { - if (!(reasons & reasonItem)) - continue; - auto result = flowStatistics.add(reasonItem, flowLength); - if (!result.isNewEntry) - result.iterator->value += flowLength; - } - } - stream << "---------------------------------------------------\n"; - stream << "Number of text blocks: total(" << leafRenderers.size() << ") non-simple(" << numberOfUnsupportedLeafBlocks << ")\nText length: total(" << - textLength << ") non-simple(" << unsupportedTextLength << ")\n"; - for (const auto reasonEntry : flowStatistics) { - printReason(reasonEntry.key, stream); - stream << ": " << (float)reasonEntry.value / (float)textLength * 100 << "%\n"; - } - stream << "simple line layout coverage: " << (float)(textLength - unsupportedTextLength) / (float)textLength * 100 << "%\n"; - stream << "---------------------------------------------------\n"; - WTFLogAlways("%s", stream.release().utf8().data()); -} -#endif } } |