bool hitTestFlow(const RenderBlockFlow& flow, const Layout& layout, const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction) { if (hitTestAction != HitTestForeground) return false; if (!layout.runCount()) return false; RenderStyle& style = flow.style(); if (style.visibility() != VISIBLE || style.pointerEvents() == PE_NONE) return false; RenderObject& renderer = *flow.firstChild(); LayoutRect rangeRect = locationInContainer.boundingBox(); rangeRect.moveBy(-accumulatedOffset); auto resolver = lineResolver(flow, layout); for (FloatRect lineRect : resolver.rangeForRect(rangeRect)) { lineRect.moveBy(accumulatedOffset); if (!locationInContainer.intersects(lineRect)) continue; renderer.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToRectBasedTestResult(renderer.node(), request, locationInContainer, lineRect)) return true; } return false; }
void RenderMultiColumnFlowThread::populate() { RenderBlockFlow* multicolContainer = multiColumnBlockFlow(); ASSERT(!nextSibling()); // Reparent children preceding the flow thread into the flow thread. It's multicol content // now. At this point there's obviously nothing after the flow thread, but renderers (column // sets and spanners) will be inserted there as we insert elements into the flow thread. LayoutStateDisabler layoutStateDisabler(&view()); multicolContainer->moveChildrenTo(this, multicolContainer->firstChild(), this, true); }
std::unique_ptr<Layout> create(RenderBlockFlow& flow) { 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(); return Layout::create(runs, lineCount); }
void paintFlow(const RenderBlockFlow& flow, const Layout& layout, PaintInfo& paintInfo, const LayoutPoint& paintOffset) { if (paintInfo.phase != PaintPhaseForeground) return; RenderStyle& style = flow.style(); if (style.visibility() != VISIBLE) return; RenderText& textRenderer = toRenderText(*flow.firstChild()); ASSERT(!textRenderer.firstTextBox()); bool debugBordersEnabled = flow.frame().settings().simpleLineLayoutDebugBordersEnabled(); GraphicsContext& context = *paintInfo.context; const Font& font = style.font(); TextPaintStyle textPaintStyle = computeTextPaintStyle(textRenderer, style, paintInfo); GraphicsContextStateSaver stateSaver(context, textPaintStyle.strokeWidth > 0); updateGraphicsContext(context, textPaintStyle); LayoutRect paintRect = paintInfo.rect; paintRect.moveBy(-paintOffset); auto resolver = runResolver(flow, layout); auto range = resolver.rangeForRect(paintRect); for (auto it = range.begin(), end = range.end(); it != end; ++it) { const auto& run = *it; if (!run.rect().intersects(paintRect)) continue; TextRun textRun(run.text()); textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize()); FloatPoint textOrigin = run.baseline() + paintOffset; textOrigin.setY(roundToDevicePixel(LayoutUnit(textOrigin.y()), flow.document().deviceScaleFactor())); context.drawText(font, textRun, textOrigin); if (debugBordersEnabled) paintDebugBorders(context, run.rect(), paintOffset); } }
bool canUseFor(const RenderBlockFlow& flow) { 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; const RenderStyle& style = flow.style(); if (style.textDecorationsInEffect() != TextDecorationNone) return false; if (style.textAlign() == JUSTIFY) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) 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()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) 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) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) return false; 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()) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) 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. float minimumWidthNeeded = textRenderer.minLogicalWidth(); for (auto& floatRenderer : *flow.floatingObjectSet()) { ASSERT(floatRenderer); float availableWidth = flow.availableLogicalWidthForLine(floatRenderer->y(), false); if (availableWidth < minimumWidthNeeded) return false; } } if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() || textRenderer.isSVGInlineText()) return false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; if (style.font().isSVGFont()) return false; // 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; }
static AvoidanceReasonFlags canUseForWithReason(const RenderBlockFlow& flow, FallThrough fallthrough) { #ifndef NDEBUG static std::once_flag onceFlag; std::call_once(onceFlag, [] { registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutCoverage", printSimpleLineLayoutCoverage); registerNotifyCallback("com.apple.WebKit.showSimpleLineLayoutReasons", printSimpleLineLayoutBlockList); }); #endif AvoidanceReasonFlags reasons = NoReason; if (!flow.frame().settings().simpleLineLayoutEnabled()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FeatureIsDisabled, fallthrough); if (!flow.parent()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasNoParent, fallthrough); if (!flow.firstChild()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasNoChild, fallthrough); if (flow.flowThreadState() != RenderObject::NotInsideFlowThread) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowIsInsideRegion, fallthrough); if (!flow.isHorizontalWritingMode()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasHorizonalWritingMode, fallthrough); if (flow.hasOutline()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasOutline, fallthrough); if (flow.isRubyText() || flow.isRubyBase()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowIsRuby, fallthrough); // Printing does pagination without a flow thread. if (flow.document().paginated()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowIsPaginated, fallthrough); if (flow.isAnonymous() && flow.firstLineBlock()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasPseudoFirstLine, fallthrough); if (flow.isAnonymousBlock() && flow.parent()->style().textOverflow()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasTextOverflow, fallthrough); if (flow.parent()->isDeprecatedFlexibleBox()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowIsDepricatedFlexBox, fallthrough); // FIXME: Placeholders do something strange. if (is<RenderTextControl>(*flow.parent()) && downcast<RenderTextControl>(*flow.parent()).textFormControlElement().placeholderElement()) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowParentIsPlaceholderElement, fallthrough); // FIXME: Implementation of wrap=hard looks into lineboxes. if (flow.parent()->isTextArea() && flow.parent()->element()->fastHasAttribute(HTMLNames::wrapAttr)) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowParentIsTextAreaWithWrapping, fallthrough); // 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 (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(reasons, FlowHasNonSupportedChild, fallthrough); break; } auto styleReasons = canUseForStyle(flow.style(), fallthrough); if (styleReasons != NoReason) SET_REASON_AND_RETURN_IF_NEEDED(reasons, styleReasons, fallthrough); // 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()); 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(reasons, FlowHasUnsupportedFloat, fallthrough); #endif float availableWidth = flow.availableLogicalWidthForLine(floatingObject->y(), false); if (availableWidth < minimumWidthNeeded) SET_REASON_AND_RETURN_IF_NEEDED(reasons, FlowHasUnsupportedFloat, fallthrough); } } } auto fontAndTextReasons = canUseForFontAndText(flow, fallthrough); if (fontAndTextReasons != NoReason) SET_REASON_AND_RETURN_IF_NEEDED(reasons, fontAndTextReasons, fallthrough); return reasons; }
bool canUseFor(const RenderBlockFlow& flow) { #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; } const RenderStyle& style = flow.style(); if (style.textDecorationsInEffect() != TextDecorationNone) return false; if (style.textAlign() == JUSTIFY) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) return false; if (!style.textIndent().isZero()) return false; if (style.wordSpacing() || style.letterSpacing()) return false; if (style.textTransform() != TTNONE) return false; if (!style.isLeftToRightDirection()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) 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) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) 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()) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) 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 false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; if (style.font().isSVGFont()) return false; // 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 }
bool canUseFor(const RenderBlockFlow& flow) { #if !PLATFORM(MAC) // FIXME: Non-mac platforms are hitting ASSERT(run.charactersLength() >= run.length()) // https://bugs.webkit.org/show_bug.cgi?id=123338 return false; #endif 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; // Supporting floats would be very beneficial. if (flow.containsFloats()) 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; // 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; } const RenderStyle& style = *flow.style(); // It shoudn't be hard to support other alignments. if (style.textAlign() != LEFT && style.textAlign() != WEBKIT_LEFT && style.textAlign() != TASTART) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) return false; // Pre/no-wrap would be very helpful to support. if (style.whiteSpace() != NORMAL) return false; if (!style.textIndent().isZero()) return false; if (style.wordSpacing() || style.letterSpacing()) return false; if (style.textTransform() != TTNONE) return false; if (!style.isLeftToRightDirection()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) 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) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) 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()) return false; if (style.overflowWrap() != NormalOverflowWrap) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) return false; const RenderText& textRenderer = toRenderText(*flow.firstChild()); if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() #if ENABLE(SVG) || textRenderer.isSVGInlineText() #endif ) return false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; auto primaryFontData = style.font().primaryFont(); unsigned length = textRenderer.textLength(); unsigned consecutiveSpaceCount = 0; for (unsigned i = 0; i < length; ++i) { // This rejects anything with more than one consecutive whitespace, except at the beginning or end. // This is because we don't currently do subruns within lines. Fixing this would improve coverage significantly. UChar character = textRenderer.characterAt(i); if (isWhitespace(character)) ++consecutiveSpaceCount; else { if (consecutiveSpaceCount != i && consecutiveSpaceCount > 1) return false; consecutiveSpaceCount = 0; } // These would be easy to support. if (character == noBreakSpace) return false; if (character == softHyphen) return false; static const UChar lowestRTLCharacter = 0x590; if (character >= lowestRTLCharacter) { 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) return false; } if (!primaryFontData->glyphForCharacter(character)) return false; } return true; }
std::unique_ptr<Lines> createLines(RenderBlockFlow& flow) { auto lines = std::make_unique<Lines>(); RenderText& textRenderer = toRenderText(*flow.firstChild()); ASSERT(!textRenderer.firstTextBox()); const RenderStyle& style = *flow.style(); const unsigned textLength = textRenderer.textLength(); float wordTrailingSpaceWidth = style.font().width(TextRun(&space, 1)); LazyLineBreakIterator lineBreakIterator(textRenderer.text(), style.locale()); int nextBreakable = -1; unsigned lineEndOffset = 0; while (lineEndOffset < textLength) { lineEndOffset = skipWhitespaces(textRenderer, lineEndOffset, textLength); unsigned lineStartOffset = lineEndOffset; unsigned runEndOffset = lineEndOffset; LineWidth lineWidth(flow, false, DoNotIndentText); while (runEndOffset < textLength) { ASSERT(!isWhitespace(textRenderer.characterAt(runEndOffset))); bool previousWasSpaceBetweenRuns = runEndOffset > lineStartOffset && isWhitespace(textRenderer.characterAt(runEndOffset - 1)); unsigned runStartOffset = previousWasSpaceBetweenRuns ? runEndOffset - 1 : runEndOffset; ++runEndOffset; while (runEndOffset < textLength) { if (runEndOffset > lineStartOffset && isBreakable(lineBreakIterator, runEndOffset, nextBreakable, false)) break; ++runEndOffset; } unsigned runLength = runEndOffset - runStartOffset; bool includeEndSpace = runEndOffset < textLength && textRenderer.characterAt(runEndOffset) == ' '; float wordWidth; if (includeEndSpace) wordWidth = textWidth(textRenderer, runStartOffset, runLength + 1, lineWidth.committedWidth(), style) - wordTrailingSpaceWidth; else wordWidth = textWidth(textRenderer, runStartOffset, runLength, lineWidth.committedWidth(), style); lineWidth.addUncommittedWidth(wordWidth); if (!lineWidth.fitsOnLine()) { if (!lineWidth.committedWidth()) { lineWidth.commit(); lineEndOffset = runEndOffset; } break; } lineWidth.commit(); lineEndOffset = runEndOffset; runEndOffset = skipWhitespaces(textRenderer, runEndOffset, textLength); } if (lineStartOffset == lineEndOffset) continue; Line line; line.textOffset = lineStartOffset; line.textLength = lineEndOffset - lineStartOffset; line.width = lineWidth.committedWidth(); lines->append(line); } textRenderer.clearNeedsLayout(); lines->shrinkToFit(); return lines; }
bool canUseFor(const RenderBlockFlow& flow) { #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; #endif 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; // Supporting floats would be very beneficial. if (flow.containsFloats()) 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; // 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; } const RenderStyle& style = flow.style(); if (style.textDecorationsInEffect() != TextDecorationNone) return false; if (style.textAlign() == JUSTIFY) return false; // Non-visible overflow should be pretty easy to support. if (style.overflowX() != OVISIBLE || style.overflowY() != OVISIBLE) return false; // Pre/no-wrap would be very helpful to support. if (style.whiteSpace() != NORMAL) return false; if (!style.textIndent().isZero()) return false; if (style.wordSpacing() || style.letterSpacing()) return false; if (style.textTransform() != TTNONE) return false; if (!style.isLeftToRightDirection()) return false; if (style.lineBoxContain() != RenderStyle::initialLineBoxContain()) return false; if (style.writingMode() != TopToBottomWritingMode) return false; if (style.lineBreak() != LineBreakAuto) 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) return false; if (style.textEmphasisFill() != TextEmphasisFillFilled || style.textEmphasisMark() != TextEmphasisMarkNone) return false; if (style.textShadow()) 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()) return false; if (style.overflowWrap() != NormalOverflowWrap) return false; if (style.backgroundClip() == TextFillBox) return false; if (style.borderFit() == BorderFitLines) return false; const RenderText& textRenderer = toRenderText(*flow.firstChild()); if (textRenderer.isCombineText() || textRenderer.isCounter() || textRenderer.isQuote() || textRenderer.isTextFragment() #if ENABLE(SVG) || textRenderer.isSVGInlineText() #endif ) return false; if (style.font().codePath(TextRun(textRenderer.text())) != Font::Simple) return false; // We assume that all lines have metrics based purely on the primary font. auto& primaryFontData = *style.font().primaryFont(); if (primaryFontData.isLoading()) return false; unsigned length = textRenderer.textLength(); for (unsigned i = 0; i < length; ++i) { UChar character = textRenderer.characterAt(i); if (character == ' ') continue; // These would be easy to support. if (character == noBreakSpace) return false; if (character == softHyphen) 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) return false; if (!primaryFontData.glyphForCharacter(character)) return false; } return true; }
std::unique_ptr<Layout> create(RenderBlockFlow& flow) { RenderText& textRenderer = toRenderText(*flow.firstChild()); ASSERT(!textRenderer.firstTextBox()); const RenderStyle& style = flow.style(); const unsigned textLength = textRenderer.textLength(); ETextAlign textAlign = style.textAlign(); float wordTrailingSpaceWidth = style.font().width(TextRun(&space, 1)); LazyLineBreakIterator lineBreakIterator(textRenderer.text(), style.locale()); int nextBreakable = -1; Layout::RunVector runs; unsigned lineCount = 0; unsigned lineEndOffset = 0; while (lineEndOffset < textLength) { lineEndOffset = skipWhitespaces(textRenderer, lineEndOffset, textLength); unsigned lineStartOffset = lineEndOffset; unsigned wordEndOffset = lineEndOffset; LineWidth lineWidth(flow, false, DoNotIndentText); Vector<Run, 4> lineRuns; lineRuns.uncheckedAppend(Run(lineStartOffset, 0)); while (wordEndOffset < textLength) { ASSERT(!isWhitespace(textRenderer.characterAt(wordEndOffset))); bool previousWasSpaceBetweenWords = wordEndOffset > lineStartOffset && isWhitespace(textRenderer.characterAt(wordEndOffset - 1)); unsigned wordStartOffset = previousWasSpaceBetweenWords ? wordEndOffset - 1 : wordEndOffset; ++wordEndOffset; while (wordEndOffset < textLength) { if (wordEndOffset > lineStartOffset && isBreakable(lineBreakIterator, wordEndOffset, nextBreakable, false)) break; ++wordEndOffset; } unsigned wordLength = wordEndOffset - wordStartOffset; bool includeEndSpace = wordEndOffset < textLength && textRenderer.characterAt(wordEndOffset) == ' '; float wordWidth; if (includeEndSpace) wordWidth = textWidth(textRenderer, wordStartOffset, wordLength + 1, lineWidth.committedWidth(), style) - wordTrailingSpaceWidth; else wordWidth = textWidth(textRenderer, wordStartOffset, wordLength, lineWidth.committedWidth(), style); lineWidth.addUncommittedWidth(wordWidth); // Move to the next line if the current one is full and we have something on it. if (!lineWidth.fitsOnLine() && lineWidth.committedWidth()) break; if (wordStartOffset > lineEndOffset) { // There were more than one consecutive whitespace. ASSERT(previousWasSpaceBetweenWords); // Include space to the end of the previous run. lineRuns.last().textLength++; lineRuns.last().right += wordTrailingSpaceWidth; // Start a new run on the same line. lineRuns.append(Run(wordStartOffset + 1, lineRuns.last().right)); } lineWidth.commit(); lineRuns.last().right = lineWidth.committedWidth(); lineRuns.last().textLength = wordEndOffset - lineRuns.last().textOffset; lineEndOffset = wordEndOffset; wordEndOffset = skipWhitespaces(textRenderer, wordEndOffset, textLength); if (!lineWidth.fitsOnLine()) { // The first run on the line overflows. ASSERT(lineRuns.size() == 1); break; } } if (lineStartOffset == lineEndOffset) continue; adjustRunOffsets(lineRuns, textAlign, lineWidth.committedWidth(), lineWidth.availableWidth()); for (unsigned i = 0; i < lineRuns.size(); ++i) runs.append(lineRuns[i]); runs.last().isEndOfLine = true; ++lineCount; } textRenderer.clearNeedsLayout(); return Layout::create(runs, lineCount); }