/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "RenderTextLineBoxes.h" #include "EllipsisBox.h" #include "InlineTextBox.h" #include "RenderBlock.h" #include "RenderStyle.h" #include "RootInlineBox.h" namespace WebCore { RenderTextLineBoxes::RenderTextLineBoxes() : m_first(nullptr) , m_last(nullptr) { } InlineTextBox* RenderTextLineBoxes::createAndAppendLineBox(RenderText& renderText) { auto textBox = renderText.createTextBox(); if (!m_first) { m_first = textBox.get(); m_last = textBox.get(); } else { m_last->setNextTextBox(textBox.get()); textBox->setPreviousTextBox(m_last); m_last = textBox.get(); } return textBox.release(); } void RenderTextLineBoxes::extract(InlineTextBox& box) { checkConsistency(); m_last = box.prevTextBox(); if (&box == m_first) m_first = nullptr; if (box.prevTextBox()) box.prevTextBox()->setNextTextBox(nullptr); box.setPreviousTextBox(nullptr); for (auto* current = &box; current; current = current->nextTextBox()) current->setExtracted(); checkConsistency(); } void RenderTextLineBoxes::attach(InlineTextBox& box) { checkConsistency(); if (m_last) { m_last->setNextTextBox(&box); box.setPreviousTextBox(m_last); } else m_first = &box; InlineTextBox* last = nullptr; for (auto* current = &box; current; current = current->nextTextBox()) { current->setExtracted(false); last = current; } m_last = last; checkConsistency(); } void RenderTextLineBoxes::remove(InlineTextBox& box) { checkConsistency(); if (&box == m_first) m_first = box.nextTextBox(); if (&box == m_last) m_last = box.prevTextBox(); if (box.nextTextBox()) box.nextTextBox()->setPreviousTextBox(box.prevTextBox()); if (box.prevTextBox()) box.prevTextBox()->setNextTextBox(box.nextTextBox()); checkConsistency(); } void RenderTextLineBoxes::removeAllFromParent(RenderText& renderer) { if (!m_first) { if (renderer.parent()) renderer.parent()->dirtyLinesFromChangedChild(renderer); return; } for (auto* box = m_first; box; box = box->nextTextBox()) box->removeFromParent(); } void RenderTextLineBoxes::deleteAll() { if (!m_first) return; InlineTextBox* next; for (auto* current = m_first; current; current = next) { next = current->nextTextBox(); delete current; } m_first = nullptr; m_last = nullptr; } InlineTextBox* RenderTextLineBoxes::findNext(int offset, int& position) const { if (!m_first) return nullptr; // FIXME: This looks buggy. The function is only used for debugging purposes. auto current = m_first; int currentOffset = current->len(); while (offset > currentOffset && current->nextTextBox()) { current = current->nextTextBox(); currentOffset = current->start() + current->len(); } // we are now in the correct text run position = (offset > currentOffset ? current->len() : current->len() - (currentOffset - offset)); return current; } IntRect RenderTextLineBoxes::boundingBox(const RenderText& renderer) const { if (!m_first) return IntRect(); // Return the width of the minimal left side and the maximal right side. float logicalLeftSide = 0; float logicalRightSide = 0; for (auto* current = m_first; current; current = current->nextTextBox()) { if (current == m_first || current->logicalLeft() < logicalLeftSide) logicalLeftSide = current->logicalLeft(); if (current == m_first || current->logicalRight() > logicalRightSide) logicalRightSide = current->logicalRight(); } bool isHorizontal = renderer.style().isHorizontalWritingMode(); float x = isHorizontal ? logicalLeftSide : m_first->x(); float y = isHorizontal ? m_first->y() : logicalLeftSide; float width = isHorizontal ? logicalRightSide - logicalLeftSide : m_last->logicalBottom() - x; float height = isHorizontal ? m_last->logicalBottom() - y : logicalRightSide - logicalLeftSide; return enclosingIntRect(FloatRect(x, y, width, height)); } IntPoint RenderTextLineBoxes::firstRunLocation() const { if (!m_first) return IntPoint(); return IntPoint(m_first->topLeft()); } LayoutRect RenderTextLineBoxes::visualOverflowBoundingBox(const RenderText& renderer) const { if (!m_first) return LayoutRect(); // Return the width of the minimal left side and the maximal right side. auto logicalLeftSide = LayoutUnit::max(); auto logicalRightSide = LayoutUnit::min(); for (auto* current = m_first; current; current = current->nextTextBox()) { logicalLeftSide = std::min(logicalLeftSide, current->logicalLeftVisualOverflow()); logicalRightSide = std::max(logicalRightSide, current->logicalRightVisualOverflow()); } auto logicalTop = m_first->logicalTopVisualOverflow(); auto logicalWidth = logicalRightSide - logicalLeftSide; auto logicalHeight = m_last->logicalBottomVisualOverflow() - logicalTop; LayoutRect rect(logicalLeftSide, logicalTop, logicalWidth, logicalHeight); if (!renderer.style().isHorizontalWritingMode()) rect = rect.transposedRect(); return rect; } bool RenderTextLineBoxes::hasRenderedText() const { for (auto* box = m_first; box; box = box->nextTextBox()) { if (box->len()) return true; } return false; } int RenderTextLineBoxes::caretMinOffset() const { auto box = m_first; if (!box) return 0; int minOffset = box->start(); for (box = box->nextTextBox(); box; box = box->nextTextBox()) minOffset = std::min(minOffset, box->start()); return minOffset; } int RenderTextLineBoxes::caretMaxOffset(const RenderText& renderer) const { auto box = m_last; if (!box) return renderer.textLength(); int maxOffset = box->start() + box->len(); for (box = box->prevTextBox(); box; box = box->prevTextBox()) maxOffset = std::max(maxOffset, box->start() + box->len()); return maxOffset; } bool RenderTextLineBoxes::containsOffset(const RenderText& renderer, unsigned offset, OffsetType type) const { for (auto* box = m_first; box; box = box->nextTextBox()) { if (offset < box->start() && !renderer.containsReversedText()) return false; unsigned boxEnd = box->start() + box->len(); if (offset >= box->start() && offset <= boxEnd) { if (offset == boxEnd && (type == CharacterOffset || box->isLineBreak())) continue; if (type == CharacterOffset) return true; // Return false for offsets inside composed characters. return !offset || offset == static_cast(renderer.nextOffset(renderer.previousOffset(offset))); } } return false; } unsigned RenderTextLineBoxes::countCharacterOffsetsUntil(unsigned offset) const { unsigned result = 0; for (auto* box = m_first; box; box = box->nextTextBox()) { if (offset < box->start()) return result; if (offset <= box->start() + box->len()) { result += offset - box->start(); return result; } result += box->len(); } return result; } enum ShouldAffinityBeDownstream { AlwaysDownstream, AlwaysUpstream, UpstreamIfPositionIsNotAtStart }; static bool lineDirectionPointFitsInBox(int pointLineDirection, const InlineTextBox& box, ShouldAffinityBeDownstream& shouldAffinityBeDownstream) { shouldAffinityBeDownstream = AlwaysDownstream; // the x coordinate is equal to the left edge of this box // the affinity must be downstream so the position doesn't jump back to the previous line // except when box is the first box in the line if (pointLineDirection <= box.logicalLeft()) { shouldAffinityBeDownstream = !box.prevLeafChild() ? UpstreamIfPositionIsNotAtStart : AlwaysDownstream; return true; } #if !PLATFORM(IOS) // and the x coordinate is to the left of the right edge of this box // check to see if position goes in this box if (pointLineDirection < box.logicalRight()) { shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart; return true; } #endif // box is first on line // and the x coordinate is to the left of the first text box left edge if (!box.prevLeafChildIgnoringLineBreak() && pointLineDirection < box.logicalLeft()) return true; if (!box.nextLeafChildIgnoringLineBreak()) { // box is last on line // and the x coordinate is to the right of the last text box right edge // generate VisiblePosition, use UPSTREAM affinity if possible shouldAffinityBeDownstream = UpstreamIfPositionIsNotAtStart; return true; } return false; } static VisiblePosition createVisiblePositionForBox(const InlineBox& box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream) { EAffinity affinity = VP_DEFAULT_AFFINITY; switch (shouldAffinityBeDownstream) { case AlwaysDownstream: affinity = DOWNSTREAM; break; case AlwaysUpstream: affinity = VP_UPSTREAM_IF_POSSIBLE; break; case UpstreamIfPositionIsNotAtStart: affinity = offset > box.caretMinOffset() ? VP_UPSTREAM_IF_POSSIBLE : DOWNSTREAM; break; } return box.renderer().createVisiblePosition(offset, affinity); } static VisiblePosition createVisiblePositionAfterAdjustingOffsetForBiDi(const InlineTextBox& box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream) { ASSERT(offset >= 0); if (offset && static_cast(offset) < box.len()) return createVisiblePositionForBox(box, box.start() + offset, shouldAffinityBeDownstream); bool positionIsAtStartOfBox = !offset; if (positionIsAtStartOfBox == box.isLeftToRightDirection()) { // offset is on the left edge const InlineBox* prevBox = box.prevLeafChildIgnoringLineBreak(); if ((prevBox && prevBox->bidiLevel() == box.bidiLevel()) || box.renderer().containingBlock()->style().direction() == box.direction()) // FIXME: left on 12CBA return createVisiblePositionForBox(box, box.caretLeftmostOffset(), shouldAffinityBeDownstream); if (prevBox && prevBox->bidiLevel() > box.bidiLevel()) { // e.g. left of B in aDC12BAb const InlineBox* leftmostBox; do { leftmostBox = prevBox; prevBox = leftmostBox->prevLeafChildIgnoringLineBreak(); } while (prevBox && prevBox->bidiLevel() > box.bidiLevel()); return createVisiblePositionForBox(*leftmostBox, leftmostBox->caretRightmostOffset(), shouldAffinityBeDownstream); } if (!prevBox || prevBox->bidiLevel() < box.bidiLevel()) { // e.g. left of D in aDC12BAb const InlineBox* rightmostBox; const InlineBox* nextBox = &box; do { rightmostBox = nextBox; nextBox = rightmostBox->nextLeafChildIgnoringLineBreak(); } while (nextBox && nextBox->bidiLevel() >= box.bidiLevel()); return createVisiblePositionForBox(*rightmostBox, box.isLeftToRightDirection() ? rightmostBox->caretMaxOffset() : rightmostBox->caretMinOffset(), shouldAffinityBeDownstream); } return createVisiblePositionForBox(box, box.caretRightmostOffset(), shouldAffinityBeDownstream); } const InlineBox* nextBox = box.nextLeafChildIgnoringLineBreak(); if ((nextBox && nextBox->bidiLevel() == box.bidiLevel()) || box.renderer().containingBlock()->style().direction() == box.direction()) return createVisiblePositionForBox(box, box.caretRightmostOffset(), shouldAffinityBeDownstream); // offset is on the right edge if (nextBox && nextBox->bidiLevel() > box.bidiLevel()) { // e.g. right of C in aDC12BAb const InlineBox* rightmostBox; do { rightmostBox = nextBox; nextBox = rightmostBox->nextLeafChildIgnoringLineBreak(); } while (nextBox && nextBox->bidiLevel() > box.bidiLevel()); return createVisiblePositionForBox(*rightmostBox, rightmostBox->caretLeftmostOffset(), shouldAffinityBeDownstream); } if (!nextBox || nextBox->bidiLevel() < box.bidiLevel()) { // e.g. right of A in aDC12BAb const InlineBox* leftmostBox; const InlineBox* prevBox = &box; do { leftmostBox = prevBox; prevBox = leftmostBox->prevLeafChildIgnoringLineBreak(); } while (prevBox && prevBox->bidiLevel() >= box.bidiLevel()); return createVisiblePositionForBox(*leftmostBox, box.isLeftToRightDirection() ? leftmostBox->caretMinOffset() : leftmostBox->caretMaxOffset(), shouldAffinityBeDownstream); } return createVisiblePositionForBox(box, box.caretLeftmostOffset(), shouldAffinityBeDownstream); } VisiblePosition RenderTextLineBoxes::positionForPoint(const RenderText& renderer, const LayoutPoint& point) const { if (!m_first || !renderer.textLength()) return renderer.createVisiblePosition(0, DOWNSTREAM); LayoutUnit pointLineDirection = m_first->isHorizontal() ? point.x() : point.y(); LayoutUnit pointBlockDirection = m_first->isHorizontal() ? point.y() : point.x(); bool blocksAreFlipped = renderer.style().isFlippedBlocksWritingMode(); InlineTextBox* lastBox = nullptr; for (auto* box = m_first; box; box = box->nextTextBox()) { if (box->isLineBreak() && !box->prevLeafChild() && box->nextLeafChild() && !box->nextLeafChild()->isLineBreak()) box = box->nextTextBox(); auto& rootBox = box->root(); LayoutUnit top = std::min(rootBox.selectionTop(), rootBox.lineTop()); if (pointBlockDirection > top || (!blocksAreFlipped && pointBlockDirection == top)) { LayoutUnit bottom = rootBox.selectionBottom(); if (rootBox.nextRootBox()) bottom = std::min(bottom, rootBox.nextRootBox()->lineTop()); if (pointBlockDirection < bottom || (blocksAreFlipped && pointBlockDirection == bottom)) { ShouldAffinityBeDownstream shouldAffinityBeDownstream; #if PLATFORM(IOS) if (pointLineDirection != box->logicalLeft() && point.x() < box->x() + box->logicalWidth()) { int half = box->x() + box->logicalWidth() / 2; EAffinity affinity = point.x() < half ? DOWNSTREAM : VP_UPSTREAM_IF_POSSIBLE; return renderer.createVisiblePosition(box->offsetForPosition(pointLineDirection) + box->start(), affinity); } #endif if (lineDirectionPointFitsInBox(pointLineDirection, *box, shouldAffinityBeDownstream)) return createVisiblePositionAfterAdjustingOffsetForBiDi(*box, box->offsetForPosition(pointLineDirection), shouldAffinityBeDownstream); } } lastBox = box; } if (lastBox) { ShouldAffinityBeDownstream shouldAffinityBeDownstream; lineDirectionPointFitsInBox(pointLineDirection, *lastBox, shouldAffinityBeDownstream); return createVisiblePositionAfterAdjustingOffsetForBiDi(*lastBox, lastBox->offsetForPosition(pointLineDirection) + lastBox->start(), shouldAffinityBeDownstream); } return renderer.createVisiblePosition(0, DOWNSTREAM); } void RenderTextLineBoxes::setSelectionState(RenderText& renderer, RenderObject::SelectionState state) { if (state == RenderObject::SelectionInside || state == RenderObject::SelectionNone) { for (auto* box = m_first; box; box = box->nextTextBox()) box->root().setHasSelectedChildren(state == RenderObject::SelectionInside); return; } int start, end; renderer.selectionStartEnd(start, end); if (state == RenderObject::SelectionStart) { end = renderer.textLength(); // to handle selection from end of text to end of line if (start && start == end) start = end - 1; } else if (state == RenderObject::SelectionEnd) start = 0; for (auto* box = m_first; box; box = box->nextTextBox()) { if (box->isSelected(start, end)) box->root().setHasSelectedChildren(true); } } static IntRect ellipsisRectForBox(const InlineTextBox& box, unsigned start, unsigned end) { unsigned short truncation = box.truncation(); if (truncation == cNoTruncation) return IntRect(); auto ellipsis = box.root().ellipsisBox(); if (!ellipsis) return IntRect(); IntRect rect; int ellipsisStartPosition = std::max(start - box.start(), 0); int ellipsisEndPosition = std::min(end - box.start(), box.len()); // The ellipsis should be considered to be selected if the end of // the selection is past the beginning of the truncation and the // beginning of the selection is before or at the beginning of the truncation. if (ellipsisEndPosition < truncation && ellipsisStartPosition > truncation) return IntRect(); return ellipsis->selectionRect(); } LayoutRect RenderTextLineBoxes::selectionRectForRange(unsigned start, unsigned end) { LayoutRect rect; for (auto* box = m_first; box; box = box->nextTextBox()) { rect.unite(box->localSelectionRect(start, end)); rect.unite(ellipsisRectForBox(*box, start, end)); } return rect; } void RenderTextLineBoxes::collectSelectionRectsForRange(unsigned start, unsigned end, Vector& rects) { for (auto* box = m_first; box; box = box->nextTextBox()) { LayoutRect rect; rect.unite(box->localSelectionRect(start, end)); rect.unite(ellipsisRectForBox(*box, start, end)); if (!rect.size().isEmpty()) rects.append(rect); } } Vector RenderTextLineBoxes::absoluteRects(const LayoutPoint& accumulatedOffset) const { Vector rects; for (auto* box = m_first; box; box = box->nextTextBox()) rects.append(enclosingIntRect(FloatRect(accumulatedOffset + box->topLeft(), box->size()))); return rects; } static FloatRect localQuadForTextBox(const InlineTextBox& box, unsigned start, unsigned end, bool useSelectionHeight) { unsigned realEnd = std::min(box.end() + 1, end); LayoutRect boxSelectionRect = box.localSelectionRect(start, realEnd); if (!boxSelectionRect.height()) return FloatRect(); if (useSelectionHeight) return boxSelectionRect; // Change the height and y position (or width and x for vertical text) // because selectionRect uses selection-specific values. if (box.isHorizontal()) { boxSelectionRect.setHeight(box.height()); boxSelectionRect.setY(box.y()); } else { boxSelectionRect.setWidth(box.width()); boxSelectionRect.setX(box.x()); } return boxSelectionRect; } Vector RenderTextLineBoxes::absoluteRectsForRange(const RenderText& renderer, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const { Vector rects; for (auto* box = m_first; box; box = box->nextTextBox()) { // Note: box->end() returns the index of the last character, not the index past it if (start <= box->start() && box->end() < end) { FloatRect boundaries = box->calculateBoundaries(); if (useSelectionHeight) { LayoutRect selectionRect = box->localSelectionRect(start, end); if (box->isHorizontal()) { boundaries.setHeight(selectionRect.height()); boundaries.setY(selectionRect.y()); } else { boundaries.setWidth(selectionRect.width()); boundaries.setX(selectionRect.x()); } } rects.append(renderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed).enclosingBoundingBox()); continue; } // FIXME: This code is wrong. It's converting local to absolute twice. http://webkit.org/b/65722 FloatRect rect = localQuadForTextBox(*box, start, end, useSelectionHeight); if (!rect.isZero()) rects.append(renderer.localToAbsoluteQuad(rect, UseTransforms, wasFixed).enclosingBoundingBox()); } return rects; } Vector RenderTextLineBoxes::absoluteQuads(const RenderText& renderer, bool* wasFixed, ClippingOption option) const { Vector quads; for (auto* box = m_first; box; box = box->nextTextBox()) { FloatRect boundaries = box->calculateBoundaries(); // Shorten the width of this text box if it ends in an ellipsis. // FIXME: ellipsisRectForBox should switch to return FloatRect soon with the subpixellayout branch. IntRect ellipsisRect = (option == ClipToEllipsis) ? ellipsisRectForBox(*box, 0, renderer.textLength()) : IntRect(); if (!ellipsisRect.isEmpty()) { if (renderer.style().isHorizontalWritingMode()) boundaries.setWidth(ellipsisRect.maxX() - boundaries.x()); else boundaries.setHeight(ellipsisRect.maxY() - boundaries.y()); } quads.append(renderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed)); } return quads; } Vector RenderTextLineBoxes::absoluteQuadsForRange(const RenderText& renderer, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const { Vector quads; for (auto* box = m_first; box; box = box->nextTextBox()) { // Note: box->end() returns the index of the last character, not the index past it if (start <= box->start() && box->end() < end) { FloatRect boundaries = box->calculateBoundaries(); if (useSelectionHeight) { LayoutRect selectionRect = box->localSelectionRect(start, end); if (box->isHorizontal()) { boundaries.setHeight(selectionRect.height()); boundaries.setY(selectionRect.y()); } else { boundaries.setWidth(selectionRect.width()); boundaries.setX(selectionRect.x()); } } quads.append(renderer.localToAbsoluteQuad(boundaries, UseTransforms, wasFixed)); continue; } FloatRect rect = localQuadForTextBox(*box, start, end, useSelectionHeight); if (!rect.isZero()) quads.append(renderer.localToAbsoluteQuad(rect, UseTransforms, wasFixed)); } return quads; } void RenderTextLineBoxes::dirtyAll() { for (auto* box = m_first; box; box = box->nextTextBox()) box->dirtyLineBoxes(); } bool RenderTextLineBoxes::dirtyRange(RenderText& renderer, unsigned start, unsigned end, int lengthDelta) { RootInlineBox* firstRootBox = nullptr; RootInlineBox* lastRootBox = nullptr; // Dirty all text boxes that include characters in between offset and offset+len. bool dirtiedLines = false; for (auto* current = m_first; current; current = current->nextTextBox()) { // FIXME: This shouldn't rely on the end of a dirty line box. See https://bugs.webkit.org/show_bug.cgi?id=97264 // Text run is entirely before the affected range. if (current->end() < start) continue; // Text run is entirely after the affected range. if (current->start() > end) { current->offsetRun(lengthDelta); auto& rootBox = current->root(); if (!firstRootBox) { firstRootBox = &rootBox; if (!dirtiedLines) { // The affected area was in between two runs. Mark the root box of the run after the affected area as dirty. firstRootBox->markDirty(); dirtiedLines = true; } } lastRootBox = &rootBox; continue; } if (current->end() >= start && current->end() <= end) { // Text run overlaps with the left end of the affected range. current->dirtyLineBoxes(); dirtiedLines = true; continue; } if (current->start() <= start && current->end() >= end) { // Text run subsumes the affected range. current->dirtyLineBoxes(); dirtiedLines = true; continue; } if (current->start() <= end && current->end() >= end) { // Text run overlaps with right end of the affected range. current->dirtyLineBoxes(); dirtiedLines = true; continue; } } // Now we have to walk all of the clean lines and adjust their cached line break information // to reflect our updated offsets. if (lastRootBox) lastRootBox = lastRootBox->nextRootBox(); if (firstRootBox) { auto previousRootBox = firstRootBox->prevRootBox(); if (previousRootBox) firstRootBox = previousRootBox; } else if (m_last) { ASSERT(!lastRootBox); firstRootBox = &m_last->root(); firstRootBox->markDirty(); dirtiedLines = true; } for (auto* current = firstRootBox; current && current != lastRootBox; current = current->nextRootBox()) { if (current->lineBreakObj() == &renderer && current->lineBreakPos() > end) current->setLineBreakPos(current->lineBreakPos() + lengthDelta); } // If the text node is empty, dirty the line where new text will be inserted. if (!m_first && renderer.parent()) { renderer.parent()->dirtyLinesFromChangedChild(renderer); dirtiedLines = true; } return dirtiedLines; } inline void RenderTextLineBoxes::checkConsistency() const { #if !ASSERT_DISABLED #ifdef CHECK_CONSISTENCY const InlineTextBox* prev = nullptr; for (auto* child = m_first; child; child = child->nextTextBox()) { ASSERT(child->renderer() == this); ASSERT(child->prevTextBox() == prev); prev = child; } ASSERT(prev == m_last); #endif #endif } #if !ASSERT_DISABLED RenderTextLineBoxes::~RenderTextLineBoxes() { ASSERT(!m_first); ASSERT(!m_last); } #endif #if !ASSERT_WITH_SECURITY_IMPLICATION_DISABLED void RenderTextLineBoxes::invalidateParentChildLists() { for (auto* box = m_first; box; box = box->nextTextBox()) box->invalidateParentChildList(); } #endif }