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<int>(minOffset, box->start()); return minOffset; }
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; }
Vector<IntRect> RenderTextLineBoxes::absoluteRects(const LayoutPoint& accumulatedOffset) const { Vector<IntRect> rects; for (auto box = m_first; box; box = box->nextTextBox()) rects.append(enclosingIntRect(FloatRect(accumulatedOffset + box->topLeft(), box->size()))); return rects; }
Vector<IntRect> RenderTextLineBoxes::absoluteRectsForRange(const RenderText& renderer, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const { Vector<IntRect> 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, 0, 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, 0, wasFixed).enclosingBoundingBox()); } return rects; }
Vector<FloatQuad> RenderTextLineBoxes::absoluteQuadsForRange(const RenderText& renderer, unsigned start, unsigned end, bool useSelectionHeight, bool* wasFixed) const { Vector<FloatQuad> 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, 0, wasFixed)); continue; } FloatRect rect = localQuadForTextBox(*box, start, end, useSelectionHeight); if (!rect.isZero()) quads.append(renderer.localToAbsoluteQuad(rect, 0, wasFixed)); } return quads; }
bool RenderTextLineBoxes::hasRenderedText() const { for (auto box = m_first; box; box = box->nextTextBox()) { if (box->len()) return true; } return false; }
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 SVGInlineTextBox::dirtyLineBoxes() { dirtyOwnLineBoxes(); // And clear any following text fragments as the text on which they // depend may now no longer exist, or glyph positions may be wrong for (InlineTextBox* nextBox = nextTextBox(); nextBox; nextBox = nextBox->nextTextBox()) nextBox->dirtyOwnLineBoxes(); }
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::collectSelectionRectsForRange(unsigned start, unsigned end, Vector<LayoutRect>& 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); } }
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; }
void SVGInlineTextBox::dirtyLineBoxes() { InlineTextBox::dirtyLineBoxes(); // Clear the now stale text fragments. clearTextFragments(); // And clear any following text fragments as the text on which they depend may // now no longer exist, or glyph positions may be wrong. InlineTextBox* nextBox = nextTextBox(); if (nextBox) nextBox->dirtyLineBoxes(); }
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 }
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; }
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); } }
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(); }
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<unsigned>(renderer.nextOffset(renderer.previousOffset(offset))); } } return false; }
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(); }
Vector<FloatQuad> RenderTextLineBoxes::absoluteQuads(const RenderText& renderer, bool* wasFixed, ClippingOption option) const { Vector<FloatQuad> 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, 0, wasFixed)); } return quads; }
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; }
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)); }
void RenderTextLineBoxes::invalidateParentChildLists() { for (auto box = m_first; box; box = box->nextTextBox()) box->invalidateParentChildList(); }
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. Go ahead and 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; }
void RenderTextLineBoxes::dirtyAll() { for (auto box = m_first; box; box = box->nextTextBox()) box->dirtyLineBoxes(); }