void RenderLineBoxList::dirtyLinesFromChangedChild(RenderBoxModelObject& container, RenderObject& child) { ASSERT(is<RenderInline>(container) || is<RenderBlockFlow>(container)); if (!container.parent() || (is<RenderBlockFlow>(container) && container.selfNeedsLayout())) return; RenderInline* inlineContainer = is<RenderInline>(container) ? &downcast<RenderInline>(container) : nullptr; InlineBox* firstBox = inlineContainer ? inlineContainer->firstLineBoxIncludingCulling() : firstLineBox(); // If we have no first line box, then just bail early. if (!firstBox) { // For an empty inline, propagate the check up to our parent, unless the parent is already dirty. if (container.isInline() && !container.ancestorLineBoxDirty()) { container.parent()->dirtyLinesFromChangedChild(container); container.setAncestorLineBoxDirty(); // Mark the container to avoid dirtying the same lines again across multiple destroy() calls of the same subtree. } return; } // Try to figure out which line box we belong in. First try to find a previous // line box by examining our siblings. If we didn't find a line box, then use our // parent's first line box. RootInlineBox* box = nullptr; RenderObject* current; for (current = child.previousSibling(); current; current = current->previousSibling()) { if (current->isFloatingOrOutOfFlowPositioned()) continue; if (current->isReplaced()) { if (auto wrapper = downcast<RenderBox>(*current).inlineBoxWrapper()) box = &wrapper->root(); } if (is<RenderLineBreak>(*current)) { if (auto wrapper = downcast<RenderLineBreak>(*current).inlineBoxWrapper()) box = &wrapper->root(); } else if (is<RenderText>(*current)) { if (InlineTextBox* textBox = downcast<RenderText>(*current).lastTextBox()) box = &textBox->root(); } else if (is<RenderInline>(*current)) { InlineBox* lastSiblingBox = downcast<RenderInline>(*current).lastLineBoxIncludingCulling(); if (lastSiblingBox) box = &lastSiblingBox->root(); } if (box) break; } if (!box) { if (inlineContainer && !inlineContainer->alwaysCreateLineBoxes()) { // https://bugs.webkit.org/show_bug.cgi?id=60778 // We may have just removed a <br> with no line box that was our first child. In this case // we won't find a previous sibling, but firstBox can be pointing to a following sibling. // This isn't good enough, since we won't locate the root line box that encloses the removed // <br>. We have to just over-invalidate a bit and go up to our parent. if (!inlineContainer->ancestorLineBoxDirty()) { inlineContainer->parent()->dirtyLinesFromChangedChild(*inlineContainer); inlineContainer->setAncestorLineBoxDirty(); // Mark the container to avoid dirtying the same lines again across multiple destroy() calls of the same subtree. } return; } box = &firstBox->root(); } // If we found a line box, then dirty it. if (box) { box->markDirty(); // Dirty the adjacent lines that might be affected. // NOTE: we dirty the previous line because RootInlineBox objects cache // the address of the first object on the next line after a BR, which we may be // invalidating here. For more info, see how RenderBlock::layoutInlineChildren // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak, // despite the name, actually returns the first RenderObject after the BR. // <rdar://problem/3849947> "Typing after pasting line does not appear until after window resize." if (RootInlineBox* prevBox = box->prevRootBox()) prevBox->markDirty(); // FIXME: We shouldn't need to always dirty the next line. This is only strictly // necessary some of the time, in situations involving BRs. if (RootInlineBox* nextBox = box->nextRootBox()) { nextBox->markDirty(); // Dedicated linebox for floats may be added as the last rootbox. If this occurs with BRs inside inlines that propagte their lineboxes to // the parent flow, we need to invalidate it explicitly. // FIXME: We should be able to figure out the actual "changed child" even when we are calling through empty inlines recursively. if (is<RenderInline>(child) && !downcast<RenderInline>(child).firstLineBoxIncludingCulling()) { auto* lastRootBox = nextBox->blockFlow().lastRootBox(); if (lastRootBox->isTrailingFloatsRootInlineBox() && !lastRootBox->isDirty()) lastRootBox->markDirty(); } } } }
void RootInlineBox::ascentAndDescentForBox(InlineBox* box, GlyphOverflowAndFallbackFontsMap& textBoxDataMap, int& ascent, int& descent, bool& affectsAscent, bool& affectsDescent) const { bool ascentDescentSet = false; // Replaced boxes will return 0 for the line-height if line-box-contain says they are // not to be included. if (box->renderer().isReplaced()) { if (lineStyle().lineBoxContain() & LineBoxContainReplaced) { ascent = box->baselinePosition(baselineType()); descent = box->lineHeight() - ascent; // Replaced elements always affect both the ascent and descent. affectsAscent = true; affectsDescent = true; } return; } Vector<const SimpleFontData*>* usedFonts = 0; GlyphOverflow* glyphOverflow = 0; if (box->isInlineTextBox()) { GlyphOverflowAndFallbackFontsMap::iterator it = textBoxDataMap.find(toInlineTextBox(box)); usedFonts = it == textBoxDataMap.end() ? 0 : &it->value.first; glyphOverflow = it == textBoxDataMap.end() ? 0 : &it->value.second; } bool includeLeading = includeLeadingForBox(box); bool includeFont = includeFontForBox(box); bool setUsedFont = false; bool setUsedFontWithLeading = false; const RenderStyle& boxLineStyle = box->lineStyle(); #if PLATFORM(IOS) if (usedFonts && !usedFonts->isEmpty() && (includeFont || (boxLineStyle.lineHeight().isNegative() && includeLeading)) && !box->renderer().document().settings()->alwaysUseBaselineOfPrimaryFont()) { #else if (usedFonts && !usedFonts->isEmpty() && (includeFont || (boxLineStyle.lineHeight().isNegative() && includeLeading))) { #endif usedFonts->append(boxLineStyle.font().primaryFont()); for (size_t i = 0; i < usedFonts->size(); ++i) { const FontMetrics& fontMetrics = usedFonts->at(i)->fontMetrics(); int usedFontAscent = fontMetrics.ascent(baselineType()); int usedFontDescent = fontMetrics.descent(baselineType()); int halfLeading = (fontMetrics.lineSpacing() - fontMetrics.height()) / 2; int usedFontAscentAndLeading = usedFontAscent + halfLeading; int usedFontDescentAndLeading = fontMetrics.lineSpacing() - usedFontAscentAndLeading; if (includeFont) { setAscentAndDescent(ascent, descent, usedFontAscent, usedFontDescent, ascentDescentSet); setUsedFont = true; } if (includeLeading) { setAscentAndDescent(ascent, descent, usedFontAscentAndLeading, usedFontDescentAndLeading, ascentDescentSet); setUsedFontWithLeading = true; } if (!affectsAscent) affectsAscent = usedFontAscent - box->logicalTop() > 0; if (!affectsDescent) affectsDescent = usedFontDescent + box->logicalTop() > 0; } } // If leading is included for the box, then we compute that box. if (includeLeading && !setUsedFontWithLeading) { int ascentWithLeading = box->baselinePosition(baselineType()); int descentWithLeading = box->lineHeight() - ascentWithLeading; setAscentAndDescent(ascent, descent, ascentWithLeading, descentWithLeading, ascentDescentSet); // Examine the font box for inline flows and text boxes to see if any part of it is above the baseline. // If the top of our font box relative to the root box baseline is above the root box baseline, then // we are contributing to the maxAscent value. Descent is similar. If any part of our font box is below // the root box's baseline, then we contribute to the maxDescent value. affectsAscent = ascentWithLeading - box->logicalTop() > 0; affectsDescent = descentWithLeading + box->logicalTop() > 0; } if (includeFontForBox(box) && !setUsedFont) { int fontAscent = boxLineStyle.fontMetrics().ascent(baselineType()); int fontDescent = boxLineStyle.fontMetrics().descent(baselineType()); setAscentAndDescent(ascent, descent, fontAscent, fontDescent, ascentDescentSet); affectsAscent = fontAscent - box->logicalTop() > 0; affectsDescent = fontDescent + box->logicalTop() > 0; } if (includeGlyphsForBox(box) && glyphOverflow && glyphOverflow->computeBounds) { setAscentAndDescent(ascent, descent, glyphOverflow->top, glyphOverflow->bottom, ascentDescentSet); affectsAscent = glyphOverflow->top - box->logicalTop() > 0; affectsDescent = glyphOverflow->bottom + box->logicalTop() > 0; glyphOverflow->top = std::min(glyphOverflow->top, std::max(0, glyphOverflow->top - boxLineStyle.fontMetrics().ascent(baselineType()))); glyphOverflow->bottom = std::min(glyphOverflow->bottom, std::max(0, glyphOverflow->bottom - boxLineStyle.fontMetrics().descent(baselineType()))); } if (includeMarginForBox(box)) { LayoutUnit ascentWithMargin = boxLineStyle.fontMetrics().ascent(baselineType()); LayoutUnit descentWithMargin = boxLineStyle.fontMetrics().descent(baselineType()); if (box->parent() && !box->renderer().isTextOrLineBreak()) { ascentWithMargin += box->boxModelObject()->borderAndPaddingBefore() + box->boxModelObject()->marginBefore(); descentWithMargin += box->boxModelObject()->borderAndPaddingAfter() + box->boxModelObject()->marginAfter(); } setAscentAndDescent(ascent, descent, ascentWithMargin, descentWithMargin, ascentDescentSet); // Treat like a replaced element, since we're using the margin box. affectsAscent = true; affectsDescent = true; } } LayoutUnit RootInlineBox::verticalPositionForBox(InlineBox* box, VerticalPositionCache& verticalPositionCache) { if (box->renderer().isTextOrLineBreak()) return box->parent()->logicalTop(); RenderBoxModelObject* renderer = box->boxModelObject(); ASSERT(renderer->isInline()); if (!renderer->isInline()) return 0; // This method determines the vertical position for inline elements. bool firstLine = isFirstLine(); if (firstLine && !renderer->document().styleSheetCollection().usesFirstLineRules()) firstLine = false; // Check the cache. bool isRenderInline = renderer->isRenderInline(); if (isRenderInline && !firstLine) { LayoutUnit verticalPosition = verticalPositionCache.get(renderer, baselineType()); if (verticalPosition != PositionUndefined) return verticalPosition; } LayoutUnit verticalPosition = 0; EVerticalAlign verticalAlign = renderer->style().verticalAlign(); if (verticalAlign == TOP || verticalAlign == BOTTOM) return 0; RenderElement* parent = renderer->parent(); if (parent->isRenderInline() && parent->style().verticalAlign() != TOP && parent->style().verticalAlign() != BOTTOM) verticalPosition = box->parent()->logicalTop(); if (verticalAlign != BASELINE) { const RenderStyle& parentLineStyle = firstLine ? parent->firstLineStyle() : parent->style(); const Font& font = parentLineStyle.font(); const FontMetrics& fontMetrics = font.fontMetrics(); int fontSize = font.pixelSize(); LineDirectionMode lineDirection = parent->isHorizontalWritingMode() ? HorizontalLine : VerticalLine; if (verticalAlign == SUB) verticalPosition += fontSize / 5 + 1; else if (verticalAlign == SUPER) verticalPosition -= fontSize / 3 + 1; else if (verticalAlign == TEXT_TOP) verticalPosition += renderer->baselinePosition(baselineType(), firstLine, lineDirection) - fontMetrics.ascent(baselineType()); else if (verticalAlign == MIDDLE) verticalPosition = (verticalPosition - LayoutUnit(fontMetrics.xHeight() / 2) - renderer->lineHeight(firstLine, lineDirection) / 2 + renderer->baselinePosition(baselineType(), firstLine, lineDirection)).round(); else if (verticalAlign == TEXT_BOTTOM) { verticalPosition += fontMetrics.descent(baselineType()); // lineHeight - baselinePosition is always 0 for replaced elements (except inline blocks), so don't bother wasting time in that case. if (!renderer->isReplaced() || renderer->isInlineBlockOrInlineTable()) verticalPosition -= (renderer->lineHeight(firstLine, lineDirection) - renderer->baselinePosition(baselineType(), firstLine, lineDirection)); } else if (verticalAlign == BASELINE_MIDDLE) verticalPosition += -renderer->lineHeight(firstLine, lineDirection) / 2 + renderer->baselinePosition(baselineType(), firstLine, lineDirection); else if (verticalAlign == LENGTH) { LayoutUnit lineHeight; //Per http://www.w3.org/TR/CSS21/visudet.html#propdef-vertical-align: 'Percentages: refer to the 'line-height' of the element itself'. if (renderer->style().verticalAlignLength().isPercent()) lineHeight = renderer->style().computedLineHeight(); else lineHeight = renderer->lineHeight(firstLine, lineDirection); verticalPosition -= valueForLength(renderer->style().verticalAlignLength(), lineHeight); } } // Store the cached value. if (isRenderInline && !firstLine) verticalPositionCache.set(renderer, baselineType(), verticalPosition); return verticalPosition; } bool RootInlineBox::includeLeadingForBox(InlineBox* box) const { if (box->renderer().isReplaced() || (box->renderer().isTextOrLineBreak() && !box->behavesLikeText())) return false; LineBoxContain lineBoxContain = renderer().style().lineBoxContain(); return (lineBoxContain & LineBoxContainInline) || (box == this && (lineBoxContain & LineBoxContainBlock)); } bool RootInlineBox::includeFontForBox(InlineBox* box) const { if (box->renderer().isReplaced() || (box->renderer().isTextOrLineBreak() && !box->behavesLikeText())) return false; if (!box->behavesLikeText() && box->isInlineFlowBox() && !toInlineFlowBox(box)->hasTextChildren()) return false; // For now map "glyphs" to "font" in vertical text mode until the bounds returned by glyphs aren't garbage. LineBoxContain lineBoxContain = renderer().style().lineBoxContain(); return (lineBoxContain & LineBoxContainFont) || (!isHorizontal() && (lineBoxContain & LineBoxContainGlyphs)); } bool RootInlineBox::includeGlyphsForBox(InlineBox* box) const { if (box->renderer().isReplaced() || (box->renderer().isTextOrLineBreak() && !box->behavesLikeText())) return false; if (!box->behavesLikeText() && box->isInlineFlowBox() && !toInlineFlowBox(box)->hasTextChildren()) return false; // FIXME: We can't fit to glyphs yet for vertical text, since the bounds returned are garbage. LineBoxContain lineBoxContain = renderer().style().lineBoxContain(); return isHorizontal() && (lineBoxContain & LineBoxContainGlyphs); } bool RootInlineBox::includeMarginForBox(InlineBox* box) const { if (box->renderer().isReplaced() || (box->renderer().isTextOrLineBreak() && !box->behavesLikeText())) return false; LineBoxContain lineBoxContain = renderer().style().lineBoxContain(); return lineBoxContain & LineBoxContainInlineBox; } bool RootInlineBox::fitsToGlyphs() const { // FIXME: We can't fit to glyphs yet for vertical text, since the bounds returned are garbage. LineBoxContain lineBoxContain = renderer().style().lineBoxContain(); return isHorizontal() && (lineBoxContain & LineBoxContainGlyphs); }