summaryrefslogtreecommitdiff
path: root/Source/WebCore/rendering/SimpleLineLayout.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2016-04-10 09:28:39 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2016-04-10 09:28:39 +0000
commit32761a6cee1d0dee366b885b7b9c777e67885688 (patch)
treed6bec92bebfb216f4126356e55518842c2f476a1 /Source/WebCore/rendering/SimpleLineLayout.cpp
parenta4e969f4965059196ca948db781e52f7cfebf19e (diff)
downloadWebKitGtk-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.cpp1234
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
}
}