InlineBox* SVGRootInlineBox::closestLeafChildForPosition(const LayoutPoint& point) { InlineBox* firstLeaf = firstLeafChild(); InlineBox* lastLeaf = lastLeafChild(); if (firstLeaf == lastLeaf) return firstLeaf; // FIXME: Check for vertical text! InlineBox* closestLeaf = 0; for (InlineBox* leaf = firstLeaf; leaf; leaf = leaf->nextLeafChild()) { if (!leaf->isSVGInlineTextBox()) continue; if (point.y() < leaf->y()) continue; if (point.y() > leaf->y() + leaf->virtualLogicalHeight()) continue; closestLeaf = leaf; if (point.x() < leaf->left() + leaf->logicalWidth()) return leaf; } return closestLeaf ? closestLeaf : lastLeaf; }
void RenderSVGText::absoluteQuads(Vector<FloatQuad>& quads) { RenderSVGRoot* root = findSVGRootObject(parent()); if (!root) return; // Don't use objectBoundingBox here, as it's unites the selection rects. Makes it hard // to spot errors, if there are any using WebInspector. Individually feed them into 'rects'. for (InlineFlowBox* flow = firstLineBox(); flow; flow = flow->nextLineBox()) { for (InlineBox* box = flow->firstChild(); box; box = box->nextOnLine()) { FloatRect boxRect(box->x(), box->y(), box->width(), box->height()); // FIXME: crawling up the parent chain to map each quad is very inefficient // we should compute the absoluteTransform outside this loop first. quads.append(localToAbsoluteQuad(boxRect)); } } }
FloatRect RenderSVGText::relativeBBox(bool includeStroke) const { FloatRect repaintRect; for (InlineRunBox* runBox = firstLineBox(); runBox; runBox = runBox->nextLineBox()) { ASSERT(runBox->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(runBox); for (InlineBox* box = flowBox->firstChild(); box; box = box->nextOnLine()) repaintRect.unite(FloatRect(box->xPos(), box->yPos(), box->width(), box->height())); } // SVG needs to include the strokeWidth(), not the textStrokeWidth(). if (includeStroke && style()->svgStyle()->hasStroke()) repaintRect.inflate(narrowPrecisionToFloat(KSVGPainterFactory::cssPrimitiveToLength(this, style()->svgStyle()->strokeWidth(), 0.0))); repaintRect.move(xPos(), yPos()); return repaintRect; }
void RenderSVGText::absoluteRects(Vector<IntRect>& rects, int, int) { RenderSVGRoot* root = findSVGRootObject(parent()); if (!root) return; // Don't use objectBoundingBox here, as it's unites the selection rects. Makes it hard // to spot errors, if there are any using WebInspector. Individually feed them into 'rects'. for (InlineRunBox* runBox = firstLineBox(); runBox; runBox = runBox->nextLineBox()) { ASSERT(runBox->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(runBox); for (InlineBox* box = flowBox->firstChild(); box; box = box->nextOnLine()) { FloatRect boxRect(box->x(), box->y(), box->width(), box->height()); // FIXME: crawling up the parent chain to map each rect is very inefficient // we should compute the absoluteTransform outside this loop first. rects.append(enclosingIntRect(localToAbsoluteQuad(boxRect).boundingBox())); } } }
void SVGRootInlineBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit, LayoutUnit) { ASSERT(paintInfo.phase == PaintPhaseForeground || paintInfo.phase == PaintPhaseSelection); ASSERT(!paintInfo.context->paintingDisabled()); bool isPrinting = renderer().document().printing(); bool hasSelection = !isPrinting && selectionState() != RenderObject::SelectionNone; PaintInfo childPaintInfo(paintInfo); if (hasSelection) { for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) { if (child->isSVGInlineTextBox()) toSVGInlineTextBox(child)->paintSelectionBackground(childPaintInfo); else if (child->isSVGInlineFlowBox()) toSVGInlineFlowBox(child)->paintSelectionBackground(childPaintInfo); } } SVGRenderingContext renderingContext(&renderer(), paintInfo, SVGRenderingContext::SaveGraphicsContext); if (renderingContext.isRenderingPrepared()) { for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) child->paint(paintInfo, paintOffset, 0, 0); } }
static void applyTextAnchorToTextChunk(SVGTextChunk& chunk) { // This method is not called for chunks containing chars aligned on a path. // -> all characters are visible, no need to check for "isHidden()" anywhere. if (chunk.anchor == TA_START) return; float shift = calculateTextAnchorShiftForTextChunk(chunk, chunk.anchor); // Apply correction to chunk Vector<SVGChar>::iterator chunkIt = chunk.start; for (; chunkIt != chunk.end; ++chunkIt) { SVGChar& curChar = *chunkIt; if (chunk.isVerticalText) curChar.y += shift; else curChar.x += shift; } // Move inline boxes Vector<SVGInlineBoxCharacterRange>::iterator boxIt = chunk.boxes.begin(); Vector<SVGInlineBoxCharacterRange>::iterator boxEnd = chunk.boxes.end(); for (; boxIt != boxEnd; ++boxIt) { SVGInlineBoxCharacterRange& range = *boxIt; InlineBox* curBox = range.box; ASSERT(curBox->isSVGInlineTextBox()); // Move target box if (chunk.isVerticalText) curBox->setY(curBox->y() + static_cast<int>(shift)); else curBox->setX(curBox->x() + static_cast<int>(shift)); } }
void RenderSVGText::absoluteRects(Vector<IntRect>& rects, int, int, bool) { RenderSVGRoot* root = findSVGRootObject(parent()); if (!root) return; int x, y; absolutePosition(x, y); AffineTransform htmlParentCtm = root->RenderContainer::absoluteTransform(); // Don't use relativeBBox here, as it's unites the selection rects. Makes it hard // to spot errors, if there are any using WebInspector. Individually feed them into 'rects'. for (InlineRunBox* runBox = firstLineBox(); runBox; runBox = runBox->nextLineBox()) { ASSERT(runBox->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(runBox); for (InlineBox* box = flowBox->firstChild(); box; box = box->nextOnLine()) { FloatRect boxRect(box->xPos(), box->yPos(), box->width(), box->height()); boxRect.move(narrowPrecisionToFloat(x - htmlParentCtm.e()), narrowPrecisionToFloat(y - htmlParentCtm.f())); rects.append(enclosingIntRect(absoluteTransform().mapRect(boxRect))); } } }
void InlineFlowBoxPainter::paint(const PaintInfo& paintInfo, const LayoutPoint& paintOffset, const LayoutUnit lineTop, const LayoutUnit lineBottom) { LayoutRect overflowRect(m_inlineFlowBox.visualOverflowRect(lineTop, lineBottom)); m_inlineFlowBox.flipForWritingMode(overflowRect); overflowRect.moveBy(paintOffset); if (!paintInfo.rect.intersects(pixelSnappedIntRect(overflowRect))) return; if (paintInfo.phase == PaintPhaseOutline || paintInfo.phase == PaintPhaseSelfOutline) { // Add ourselves to the paint info struct's list of inlines that need to paint their // outlines. if (m_inlineFlowBox.layoutObject().style()->visibility() == VISIBLE && m_inlineFlowBox.layoutObject().style()->hasOutline() && !m_inlineFlowBox.isRootInlineBox()) { LayoutInline& inlineFlow = toLayoutInline(m_inlineFlowBox.layoutObject()); LayoutBlock* cb = 0; bool containingBlockPaintsContinuationOutline = inlineFlow.continuation() || inlineFlow.isInlineElementContinuation(); if (containingBlockPaintsContinuationOutline) { // FIXME: See https://bugs.webkit.org/show_bug.cgi?id=54690. We currently don't reconnect inline continuations // after a child removal. As a result, those merged inlines do not get seperated and hence not get enclosed by // anonymous blocks. In this case, it is better to bail out and paint it ourself. LayoutBlock* enclosingAnonymousBlock = m_inlineFlowBox.layoutObject().containingBlock(); if (!enclosingAnonymousBlock->isAnonymousBlock()) { containingBlockPaintsContinuationOutline = false; } else { cb = enclosingAnonymousBlock->containingBlock(); for (LayoutBoxModelObject* box = m_inlineFlowBox.boxModelObject(); box != cb; box = box->parent()->enclosingBoxModelObject()) { if (box->hasSelfPaintingLayer()) { containingBlockPaintsContinuationOutline = false; break; } } } } if (containingBlockPaintsContinuationOutline) { // Add ourselves to the containing block of the entire continuation so that it can // paint us atomically. cb->addContinuationWithOutline(toLayoutInline(m_inlineFlowBox.layoutObject().node()->layoutObject())); } else if (!inlineFlow.isInlineElementContinuation()) { paintInfo.outlineObjects()->add(&inlineFlow); } } } else if (paintInfo.phase == PaintPhaseMask) { DrawingRecorder recorder(*paintInfo.context, m_inlineFlowBox, DisplayItem::paintPhaseToDrawingType(paintInfo.phase), pixelSnappedIntRect(overflowRect)); if (!recorder.canUseCachedDrawing()) paintMask(paintInfo, paintOffset); return; } else if (paintInfo.phase == PaintPhaseForeground) { // Paint our background, border and box-shadow. paintBoxDecorationBackground(paintInfo, paintOffset); } // Paint our children. if (paintInfo.phase != PaintPhaseSelfOutline) { PaintInfo childInfo(paintInfo); childInfo.phase = paintInfo.phase == PaintPhaseChildOutlines ? PaintPhaseOutline : paintInfo.phase; if (childInfo.paintingRoot && childInfo.paintingRoot->isDescendantOf(&m_inlineFlowBox.layoutObject())) childInfo.paintingRoot = 0; else childInfo.updatePaintingRootForChildren(&m_inlineFlowBox.layoutObject()); for (InlineBox* curr = m_inlineFlowBox.firstChild(); curr; curr = curr->nextOnLine()) { if (curr->layoutObject().isText() || !curr->boxModelObject()->hasSelfPaintingLayer()) curr->paint(childInfo, paintOffset, lineTop, lineBottom); } } }
unsigned char RenderedPosition::bidiLevelOnRight() const { InlineBox* box = atRightmostOffsetInBox() ? nextLeafChild() : m_inlineBox; return box ? box->bidiLevel() : 0; }
void RenderImage::paint(PaintInfo& i, int _tx, int _ty) { if (!shouldPaint(i, _tx, _ty)) return; _tx += m_x; _ty += m_y; if (shouldPaintBackgroundOrBorder() && i.phase != PaintActionOutline) paintBoxDecorations(i, _tx, _ty); QPainter* p = i.p; if (i.phase == PaintActionOutline && style()->outlineWidth() && style()->visibility() == VISIBLE) paintOutline(p, _tx, _ty, width(), height(), style()); if (i.phase != PaintActionForeground && i.phase != PaintActionSelection) return; if (!shouldPaintWithinRoot(i)) return; #if APPLE_CHANGES bool drawSelectionTint = selectionState() != SelectionNone; if (i.phase == PaintActionSelection) { if (selectionState() == SelectionNone) { return; } drawSelectionTint = false; } #endif int cWidth = contentWidth(); int cHeight = contentHeight(); int leftBorder = borderLeft(); int topBorder = borderTop(); int leftPad = paddingLeft(); int topPad = paddingTop(); if (khtml::printpainter && !canvas()->printImages()) return; //kdDebug( 6040 ) << " contents (" << contentWidth << "/" << contentHeight << ") border=" << borderLeft() << " padding=" << paddingLeft() << endl; if ( pix.isNull() || berrorPic) { if (i.phase == PaintActionSelection) return; if(cWidth > 2 && cHeight > 2) { #if APPLE_CHANGES if ( !berrorPic ) { p->setPen (Qt::lightGray); p->setBrush (Qt::NoBrush); p->drawRect (_tx + leftBorder + leftPad, _ty + topBorder + topPad, cWidth, cHeight); } bool errorPictureDrawn = false; int imageX = 0, imageY = 0; int usableWidth = cWidth - leftBorder - borderRight() - leftPad - paddingRight(); int usableHeight = cHeight - topBorder - borderBottom() - topPad - paddingBottom(); if(berrorPic && !pix.isNull() && (usableWidth >= pix.width()) && (usableHeight >= pix.height()) ) { // Center the error image, accounting for border and padding. int centerX = (usableWidth - pix.width())/2; if (centerX < 0) centerX = 0; int centerY = (usableHeight - pix.height())/2; if (centerY < 0) centerY = 0; imageX = leftBorder + leftPad + centerX; imageY = topBorder + topPad + centerY; p->drawPixmap( QPoint(_tx + imageX, _ty + imageY), pix, pix.rect() ); errorPictureDrawn = true; } if(!alt.isEmpty()) { QString text = alt.string(); text.replace('\\', backslashAsCurrencySymbol()); p->setFont (style()->font()); p->setPen (style()->color()); int ax = _tx + leftBorder + leftPad; int ay = _ty + topBorder + topPad; const QFontMetrics &fm = style()->fontMetrics(); int ascent = fm.ascent(); // Only draw the alt text if it'll fit within the content box, // and only if it fits above the error image. int textWidth = fm.width (text, text.length()); if (errorPictureDrawn){ if (usableWidth > textWidth && fm.height() <= imageY) p->drawText(ax, ay+ascent, 0 /* ignored */, 0 /* ignored */, Qt::WordBreak /* not supported */, text ); } else if (usableWidth >= textWidth && cHeight>=fm.height()) p->drawText(ax, ay+ascent, 0 /* ignored */, 0 /* ignored */, Qt::WordBreak /* not supported */, text ); } #else /* not APPLE_CHANGES */ if ( !berrorPic ) { //qDebug("qDrawShadePanel %d/%d/%d/%d", _tx + leftBorder, _ty + topBorder, cWidth, cHeight); qDrawShadePanel( p, _tx + leftBorder + leftPad, _ty + topBorder + topPad, cWidth, cHeight, KApplication::palette().inactive(), true, 1 ); } if(berrorPic && !pix.isNull() && (cWidth >= pix.width()+4) && (cHeight >= pix.height()+4) ) { QRect r(pix.rect()); r = r.intersect(QRect(0, 0, cWidth-4, cHeight-4)); p->drawPixmap( QPoint( _tx + leftBorder + leftPad+2, _ty + topBorder + topPad+2), pix, r ); } if(!alt.isEmpty()) { QString text = alt.string(); text.replace('\\', backslashAsCurrencySymbol()); p->setFont(style()->font()); p->setPen( style()->color() ); int ax = _tx + leftBorder + leftPad + 2; int ay = _ty + topBorder + topPad + 2; const QFontMetrics &fm = style()->fontMetrics(); if (cWidth>5 && cHeight>=fm.height()) p->drawText(ax, ay+1, cWidth - 4, cHeight - 4, Qt::WordBreak, text ); } #endif /* APPLE_CHANGES not defined */ } } else if (image && !image->isTransparent()) { #if APPLE_CHANGES // Do the calculations to draw selections as tall as the line. // Ignore the passed-in value for _ty. // Use the bottom of the line above as the y position (if there is one, // otherwise use the top of this renderer's line) and the height of the line as the height. // This mimics Cocoa. int selectionTop = -1; int selectionHeight = -1; int selectionLeft = -1; int selectionRight = -1; bool extendSelectionToLeft = false; bool extendSelectionToRight = false; if (drawSelectionTint) { InlineBox *box = inlineBox(); if (box) { // Get a value for selectionTop that is relative to the containing block. // This value is used for determining left and right offset for the selection, if necessary, // and for calculating the selection height. if (box->root()->prevRootBox()) selectionTop = box->root()->prevRootBox()->bottomOverflow(); else selectionTop = box->root()->topOverflow(); selectionHeight = box->root()->bottomOverflow() - selectionTop; int absx, absy; containingBlock()->absolutePosition(absx, absy); if (selectionState() == SelectionInside && box->root()->firstLeafChild() == box) { extendSelectionToLeft = true; selectionLeft = absx + containingBlock()->leftOffset(selectionTop); } if (selectionState() == SelectionInside && box->root()->lastLeafChild() == box) { extendSelectionToRight = true; selectionRight = absx + containingBlock()->rightOffset(selectionTop); } // Now make the selectionTop an absolute coordinate. selectionTop += absy; } } #endif if ( (cWidth != intrinsicWidth() || cHeight != intrinsicHeight()) && pix.width() > 0 && pix.height() > 0 && image->valid_rect().isValid()) { #if APPLE_CHANGES QSize tintSize; #endif if (resizeCache.isNull() && cWidth && cHeight) { QRect scaledrect(image->valid_rect()); // kdDebug(6040) << "time elapsed: " << dt->elapsed() << endl; // kdDebug( 6040 ) << "have to scale: " << endl; // qDebug("cw=%d ch=%d pw=%d ph=%d rcw=%d, rch=%d", // cWidth, cHeight, intrinsicWidth(), intrinsicHeight(), resizeCache.width(), resizeCache.height()); QWMatrix matrix; matrix.scale( (float)(cWidth)/intrinsicWidth(), (float)(cHeight)/intrinsicHeight() ); resizeCache = pix.xForm( matrix ); scaledrect.setWidth( ( cWidth*scaledrect.width() ) / intrinsicWidth() ); scaledrect.setHeight( ( cHeight*scaledrect.height() ) / intrinsicHeight() ); // qDebug("resizeCache size: %d/%d", resizeCache.width(), resizeCache.height()); // qDebug("valid: %d/%d, scaled: %d/%d", // image->valid_rect().width(), image->valid_rect().height(), // scaledrect.width(), scaledrect.height()); // sometimes scaledrect.width/height are off by one because // of rounding errors. if the image is fully loaded, we // make sure that we don't do unnecessary resizes during painting QSize s(scaledrect.size()); if(QSize::equals(image->valid_rect().size(),QSize( intrinsicWidth(), intrinsicHeight() ))) // fully loaded s = QSize(cWidth, cHeight); if(QABS(s.width() - cWidth) < 2) // rounding errors s.setWidth(cWidth); if(resizeCache.size() != s) resizeCache.resize(s); p->drawPixmap( QPoint( _tx + leftBorder + leftPad, _ty + topBorder + topPad), resizeCache, scaledrect ); #if APPLE_CHANGES tintSize = s; #endif } else { p->drawPixmap( QPoint( _tx + leftBorder + leftPad, _ty + topBorder + topPad), resizeCache ); #if APPLE_CHANGES tintSize = resizeCache.rect().size(); #endif } #if APPLE_CHANGES if (drawSelectionTint) { int left = _tx + leftBorder + leftPad; int width = tintSize.width(); int top = selectionTop >= 0 ? selectionTop : _ty + topBorder + topPad; int height = selectionHeight >= 0 ? selectionHeight : tintSize.height(); QBrush brush(selectionTintColor(p)); p->fillRect(left, top, width, height, brush); if (extendSelectionToLeft) p->fillRect(selectionLeft, selectionTop, left - selectionLeft, selectionHeight, brush); if (extendSelectionToRight) p->fillRect(left + width, selectionTop, selectionRight - (left + width), selectionHeight, brush); } #endif } else { // we might be just about switching images // so pix contains the old one (we want to paint), but image->valid_rect is still invalid // so use intrinsic Size instead. // ### maybe no progressive loading for the second image ? QRect rect(image->valid_rect().isValid() ? image->valid_rect() : QRect(0, 0, intrinsicWidth(), intrinsicHeight())); QPoint offs( _tx + leftBorder + leftPad, _ty + topBorder + topPad); // qDebug("normal paint rect %d/%d/%d/%d", rect.x(), rect.y(), rect.width(), rect.height()); // rect = rect & QRect( 0 , y - offs.y() - 10, w, 10 + y + h - offs.y()); // qDebug("normal paint rect after %d/%d/%d/%d", rect.x(), rect.y(), rect.width(), rect.height()); // qDebug("normal paint: offs.y(): %d, y: %d, diff: %d", offs.y(), y, y - offs.y()); // qDebug(""); // p->setClipRect(QRect(x,y,w,h)); // p->drawPixmap( offs.x(), y, pix, rect.x(), rect.y(), rect.width(), rect.height() ); #if APPLE_CHANGES && !KWIQ HTMLImageElementImpl* i = (element() && element()->id() == ID_IMG) ? static_cast<HTMLImageElementImpl*>(element()) : 0; if (i && !i->compositeOperator().isNull()){ p->drawPixmap (offs, pix, rect, i->compositeOperator()); } else { #endif p->drawPixmap(offs, pix, rect); #if APPLE_CHANGES && !KWIQ } #endif #if APPLE_CHANGES if (drawSelectionTint) { p->fillRect(offs.x() + rect.x(), offs.y() + rect.y(), rect.width(), rect.height(), QBrush(selectionTintColor(p))); int left = offs.x() + rect.x(); int width = rect.width(); int top = selectionTop >= 0 ? selectionTop : offs.y() + rect.y(); int height = selectionHeight >= 0 ? selectionHeight : rect.height(); QBrush brush(selectionTintColor(p)); p->fillRect(left, top, width, height, brush); if (extendSelectionToLeft) p->fillRect(selectionLeft, selectionTop, left - selectionLeft, selectionHeight, brush); if (extendSelectionToRight) p->fillRect(left + width, selectionTop, selectionRight - (left + width), selectionHeight, brush); } #endif } } }
VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) { Position p = visiblePosition.deepEquivalent(); Node *node = p.node(); Node* highestRoot = highestEditableRoot(p); if (!node) return VisiblePosition(); node->document()->updateLayoutIgnorePendingStylesheets(); RenderObject *renderer = node->renderer(); if (!renderer) return VisiblePosition(); RenderBlock *containingBlock = 0; RootInlineBox *root = 0; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); if (box) { root = box->root()->nextRootBox(); if (root) containingBlock = renderer->containingBlock(); } if (!root) { // This containing editable block does not have a next line. // Need to move forward to next containing editable block in this root editable // block and find the first root line box in that block. Node* startBlock = enclosingNodeWithNonInlineRenderer(node); Node* n = nextLeafWithSameEditability(node, p.deprecatedEditingOffset()); while (n && startBlock == enclosingNodeWithNonInlineRenderer(n)) n = nextLeafWithSameEditability(n); while (n) { if (highestEditableRoot(Position(n, 0)) != highestRoot) break; Position pos(n, caretMinOffset(n)); if (pos.isCandidate()) { ASSERT(n->renderer()); pos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset); if (box) { // next root line box found root = box->root(); containingBlock = n->renderer()->containingBlock(); break; } return VisiblePosition(pos, DOWNSTREAM); } n = nextLeafWithSameEditability(n); } } if (root) { // FIXME: Can be wrong for multi-column layout and with transforms. FloatPoint absPos = containingBlock->localToAbsolute(FloatPoint()); if (containingBlock->hasOverflowClip()) absPos -= containingBlock->layer()->scrolledContentOffset(); RenderObject* renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (node && editingIgnoresContent(node)) return Position(node->parent(), node->nodeIndex()); return renderer->positionForPoint(IntPoint(x - absPos.x(), root->lineTop())); } // Could not find a next line. This means we must already be on the last line. // Move to the end of the content in this block, which effectively moves us // to the end of the line we're on. Element* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement(); return VisiblePosition(rootElement, rootElement ? rootElement->childNodeCount() : 0, DOWNSTREAM); }
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(); } } } }
GapRects RootInlineBox::fillLineSelectionGap(int selTop, int selHeight, RenderBlock* rootBlock, int blockX, int blockY, int tx, int ty, const RenderObject::PaintInfo* paintInfo) { RenderObject::SelectionState lineState = selectionState(); bool leftGap, rightGap; block()->getHorizontalSelectionGapInfo(lineState, leftGap, rightGap); GapRects result; InlineBox* firstBox = firstSelectedBox(); InlineBox* lastBox = lastSelectedBox(); if (leftGap) result.uniteLeft(block()->fillLeftSelectionGap(firstBox->parent()->object(), firstBox->xPos(), selTop, selHeight, rootBlock, blockX, blockY, tx, ty, paintInfo)); if (rightGap) result.uniteRight(block()->fillRightSelectionGap(lastBox->parent()->object(), lastBox->xPos() + lastBox->width(), selTop, selHeight, rootBlock, blockX, blockY, tx, ty, paintInfo)); if (firstBox && firstBox != lastBox) { // Now fill in any gaps on the line that occurred between two selected elements. int lastX = firstBox->xPos() + firstBox->width(); for (InlineBox* box = firstBox->nextLeafChild(); box; box = box->nextLeafChild()) { if (box->selectionState() != RenderObject::SelectionNone) { result.uniteCenter(block()->fillHorizontalSelectionGap(box->parent()->object(), lastX + tx, selTop + ty, box->xPos() - lastX, selHeight, paintInfo)); lastX = box->xPos() + box->width(); } if (box == lastBox) break; } } return result; }
void SVGRootInlineBox::markDirty() { for (InlineBox* child = firstChild(); child; child = child->nextOnLine()) child->markDirty(); RootInlineBox::markDirty(); }
void RenderLineBoxList::dirtyLinesFromChangedChild(RenderObject* container, RenderObject* child) { if (!container->parent() || (container->isRenderBlock() && (container->selfNeedsLayout() || !container->isBlockFlow()))) return; RenderInline* inlineContainer = container->isRenderInline() ? toRenderInline(container) : 0; InlineBox* firstBox = inlineContainer ? inlineContainer->firstLineBoxIncludingCulling() : firstLineBox(); // If we have no first line box, then just bail early. if (!firstBox) { // For an empty inline, go ahead and 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 = 0; RenderObject* curr = 0; for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) { if (curr->isFloatingOrOutOfFlowPositioned()) continue; if (curr->isReplaced()) { InlineBox* wrapper = toRenderBox(curr)->inlineBoxWrapper(); if (wrapper) box = wrapper->root(); } else if (curr->isText()) { InlineTextBox* textBox = toRenderText(curr)->lastTextBox(); if (textBox) box = textBox->root(); } else if (curr->isRenderInline()) { InlineBox* lastSiblingBox = toRenderInline(curr)->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) { RootInlineBox* adjacentBox; 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." adjacentBox = box->prevRootBox(); if (adjacentBox) adjacentBox->markDirty(); adjacentBox = box->nextRootBox(); // If |child| has been inserted before the first element in the linebox, but after collapsed leading // space, the search for |child|'s linebox will go past the leading space to the previous linebox and select that // one as |box|. If we hit that situation here, dirty the |box| actually containing the child too. bool insertedAfterLeadingSpace = box->lineBreakObj() == child->previousSibling(); if (adjacentBox && (adjacentBox->lineBreakObj() == child || child->isBR() || (curr && curr->isBR()) || insertedAfterLeadingSpace)) adjacentBox->markDirty(); } }
Position VisiblePosition::rightVisuallyDistinctCandidate() const { Position p = m_deepPosition; if (p.isNull()) return Position(); Position downstreamStart = mostForwardCaretPosition(p); TextDirection primaryDirection = primaryDirectionOf(*p.anchorNode()); while (true) { InlineBoxPosition boxPosition = computeInlineBoxPosition(p, m_affinity, primaryDirection); InlineBox* box = boxPosition.inlineBox; int offset = boxPosition.offsetInBox; if (!box) return primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); LayoutObject* layoutObject = &box->layoutObject(); while (true) { if ((layoutObject->isReplaced() || layoutObject->isBR()) && offset == box->caretLeftmostOffset()) return box->isLeftToRightDirection() ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); if (!layoutObject->node()) { box = box->nextLeafChild(); if (!box) return primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); layoutObject = &box->layoutObject(); offset = box->caretLeftmostOffset(); continue; } offset = box->isLeftToRightDirection() ? layoutObject->nextOffset(offset) : layoutObject->previousOffset(offset); int caretMinOffset = box->caretMinOffset(); int caretMaxOffset = box->caretMaxOffset(); if (offset > caretMinOffset && offset < caretMaxOffset) break; if (box->isLeftToRightDirection() ? offset > caretMaxOffset : offset < caretMinOffset) { // Overshot to the right. InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak(); if (!nextBox) { Position positionOnRight = primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); if (positionOnRight.isNull()) return Position(); InlineBox* boxOnRight = computeInlineBoxPosition(positionOnRight, m_affinity, primaryDirection).inlineBox; if (boxOnRight && boxOnRight->root() == box->root()) return Position(); return positionOnRight; } // Reposition at the other logical position corresponding to our edge's visual position and go for another round. box = nextBox; layoutObject = &box->layoutObject(); offset = nextBox->caretLeftmostOffset(); continue; } ASSERT(offset == box->caretRightmostOffset()); unsigned char level = box->bidiLevel(); InlineBox* nextBox = box->nextLeafChild(); if (box->direction() == primaryDirection) { if (!nextBox) { InlineBox* logicalEnd = 0; if (primaryDirection == LTR ? box->root().getLogicalEndBoxWithNode(logicalEnd) : box->root().getLogicalStartBoxWithNode(logicalEnd)) { box = logicalEnd; layoutObject = &box->layoutObject(); offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); } break; } if (nextBox->bidiLevel() >= level) break; level = nextBox->bidiLevel(); InlineBox* prevBox = box; do { prevBox = prevBox->prevLeafChild(); } while (prevBox && prevBox->bidiLevel() > level); if (prevBox && prevBox->bidiLevel() == level) // For example, abc FED 123 ^ CBA break; // For example, abc 123 ^ CBA or 123 ^ CBA abc box = nextBox; layoutObject = &box->layoutObject(); offset = box->caretLeftmostOffset(); if (box->direction() == primaryDirection) break; continue; } while (nextBox && !nextBox->layoutObject().node()) nextBox = nextBox->nextLeafChild(); if (nextBox) { box = nextBox; layoutObject = &box->layoutObject(); offset = box->caretLeftmostOffset(); if (box->bidiLevel() > level) { do { nextBox = nextBox->nextLeafChild(); } while (nextBox && nextBox->bidiLevel() > level); if (!nextBox || nextBox->bidiLevel() < level) continue; } } else { // Trailing edge of a secondary run. Set to the leading edge of the entire run. while (true) { while (InlineBox* prevBox = box->prevLeafChild()) { if (prevBox->bidiLevel() < level) break; box = prevBox; } if (box->bidiLevel() == level) break; level = box->bidiLevel(); while (InlineBox* nextBox = box->nextLeafChild()) { if (nextBox->bidiLevel() < level) break; box = nextBox; } if (box->bidiLevel() == level) break; level = box->bidiLevel(); } layoutObject = &box->layoutObject(); offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); } break; } p = Position::editingPositionOf(layoutObject->node(), offset); if ((isVisuallyEquivalentCandidate(p) && mostForwardCaretPosition(p) != downstreamStart) || p.atStartOfTree() || p.atEndOfTree()) return p; ASSERT(p != m_deepPosition); } }
InlineBox* InlineBox::prevLeafChildIgnoringLineBreak() const { InlineBox* leaf = prevLeafChild(); return (leaf && leaf->isLineBreak()) ? nullptr : leaf; }
VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int x) { Position p = visiblePosition.deepEquivalent(); Node *node = p.node(); Node* highestRoot = highestEditableRoot(p); if (!node) return VisiblePosition(); node->document()->updateLayoutIgnorePendingStylesheets(); RenderObject *renderer = node->renderer(); if (!renderer) return VisiblePosition(); RenderBlock *containingBlock = 0; RootInlineBox *root = 0; InlineBox* box; int ignoredCaretOffset; visiblePosition.getInlineBoxAndOffset(box, ignoredCaretOffset); if (box) { root = box->root()->prevRootBox(); if (root) containingBlock = renderer->containingBlock(); } if (!root) { // This containing editable block does not have a previous line. // Need to move back to previous containing editable block in this root editable // block and find the last root line box in that block. Node* startBlock = enclosingBlock(node); Node* n = previousLeafWithSameEditability(node); while (n && startBlock == enclosingBlock(n)) n = previousLeafWithSameEditability(n); while (n) { if (highestEditableRoot(Position(n, 0)) != highestRoot) break; Position pos(n, caretMinOffset(n)); if (pos.isCandidate()) { ASSERT(n->renderer()); Position maxPos(n, caretMaxOffset(n)); maxPos.getInlineBoxAndOffset(DOWNSTREAM, box, ignoredCaretOffset); if (box) { // previous root line box found root = box->root(); containingBlock = n->renderer()->containingBlock(); break; } return VisiblePosition(pos, DOWNSTREAM); } n = previousLeafWithSameEditability(n); } } if (root) { // FIXME: Can be wrong for multi-column layout. int absx, absy; containingBlock->absolutePositionForContent(absx, absy); if (containingBlock->hasOverflowClip()) containingBlock->layer()->subtractScrollOffset(absx, absy); RenderObject *renderer = root->closestLeafChildForXPos(x - absx, isEditablePosition(p))->object(); Node* node = renderer->element(); if (editingIgnoresContent(node)) return Position(node->parent(), node->nodeIndex()); return renderer->positionForCoordinates(x - absx, root->topOverflow()); } // Could not find a previous line. This means we must already be on the first line. // Move to the start of the content in this block, which effectively moves us // to the start of the line we're on. Node* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement(); return VisiblePosition(rootElement, 0, DOWNSTREAM); }
GapRects RootInlineBox::fillLineSelectionGap(int selTop, int selHeight, RenderBlock* rootBlock, int blockX, int blockY, int tx, int ty, const RenderObject::PaintInfo* paintInfo) { RenderObject::SelectionState lineState = selectionState(); bool leftGap, rightGap; block()->getHorizontalSelectionGapInfo(lineState, leftGap, rightGap); GapRects result; InlineBox* firstBox = firstSelectedBox(); InlineBox* lastBox = lastSelectedBox(); if (leftGap) result.uniteLeft(block()->fillLeftSelectionGap(firstBox->parent()->renderer(), firstBox->x(), selTop, selHeight, rootBlock, blockX, blockY, tx, ty, paintInfo)); if (rightGap) result.uniteRight(block()->fillRightSelectionGap(lastBox->parent()->renderer(), lastBox->x() + lastBox->width(), selTop, selHeight, rootBlock, blockX, blockY, tx, ty, paintInfo)); // When dealing with bidi text, a non-contiguous selection region is possible. // e.g. The logical text aaaAAAbbb (capitals denote RTL text and non-capitals LTR) is layed out // visually as 3 text runs |aaa|bbb|AAA| if we select 4 characters from the start of the text the // selection will look like (underline denotes selection): // |aaa|bbb|AAA| // ___ _ // We can see that the |bbb| run is not part of the selection while the runs around it are. if (firstBox && firstBox != lastBox) { // Now fill in any gaps on the line that occurred between two selected elements. int lastX = firstBox->x() + firstBox->width(); bool isPreviousBoxSelected = firstBox->selectionState() != RenderObject::SelectionNone; for (InlineBox* box = firstBox->nextLeafChild(); box; box = box->nextLeafChild()) { if (box->selectionState() != RenderObject::SelectionNone) { if (isPreviousBoxSelected) // VisibleSelection may be non-contiguous, see comment above. result.uniteCenter(block()->fillHorizontalSelectionGap(box->parent()->renderer(), lastX + tx, selTop + ty, box->x() - lastX, selHeight, paintInfo)); lastX = box->x() + box->width(); } if (box == lastBox) break; isPreviousBoxSelected = box->selectionState() != RenderObject::SelectionNone; } } return result; }
static int placePositionedBoxesHorizontally(InlineFlowBox* flow, int x, int& leftPosition, int& rightPosition, int& leftAlign, int& rightAlign, bool& needsWordSpacing, int xPos, bool positioned) { int mn = INT_MAX; int mx = INT_MIN; int amn = INT_MAX; int amx = INT_MIN; int startx = x; bool seenPositionedElement = false; flow->setXPos(x); for (InlineBox* curr = flow->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) { mn = min(mn, x); amn = min(amn, x); InlineTextBox* text = static_cast<InlineTextBox*>(curr); RenderText* rt = static_cast<RenderText*>(text->object()); if (rt->textLength()) { if (needsWordSpacing && DeprecatedChar(rt->characters()[text->start()]).isSpace()) x += rt->style(flow->isFirstLineStyle())->font().wordSpacing(); needsWordSpacing = !DeprecatedChar(rt->characters()[text->end()]).isSpace(); } text->setXPos(x); x += text->width(); mx = max(mx, x); amx = max(amx, x); } else if (curr->object()->isInlineFlow()) { InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); if (flow->object()->element()->hasTagName(aTag)) { x = placePositionedBoxesHorizontally(flow, x, mn, mx, amn, amx, needsWordSpacing, xPos, false); } else { SVGTextPositioningElement* text = static_cast<SVGTextPositioningElement*>(flow->object()->element()); x += (int)(text->dx()->getFirst().value()); if (text->x()->numberOfItems() > 0) x = (int)(text->x()->getFirst().value() - xPos); if (text->x()->numberOfItems() > 0 || text->y()->numberOfItems() > 0 || text->dx()->numberOfItems() > 0 || text->dy()->numberOfItems() > 0) { seenPositionedElement = true; needsWordSpacing = false; int ignoreX, ignoreY; x = placePositionedBoxesHorizontally(flow, x, mn, mx, ignoreX, ignoreY, needsWordSpacing, xPos, true); } else if (seenPositionedElement) { int ignoreX, ignoreY; x = placePositionedBoxesHorizontally(flow, x, mn, mx, ignoreX, ignoreY, needsWordSpacing, xPos, false); } else x = placePositionedBoxesHorizontally(flow, x, mn, mx, amn, amx, needsWordSpacing, xPos, false); } } } if (mn > mx) mn = mx = startx; if (amn > amx) amn = amx = startx; int width = mx - mn; flow->setWidth(width); int awidth = amx - amn; int dx = 0; if (positioned) { switch (flow->object()->style()->svgStyle()->textAnchor()) { case TA_MIDDLE: translateBox(flow, dx = -awidth / 2, 0, true); break; case TA_END: translateBox(flow, dx = -awidth, 0, true); break; case TA_START: default: break; } if (dx) { x += dx; mn += dx; mx += dx; } } leftPosition = min(leftPosition, mn); rightPosition = max(rightPosition, mx); leftAlign = min(leftAlign, amn); rightAlign = max(rightAlign, amx); return x; }
void RenderFlow::dirtyLinesFromChangedChild(RenderObject* child, bool adding) { if (!parent() || selfNeedsLayout() || isTable()) return; // For an empty inline, go ahead and propagate the check up to our parent. if (isInline() && !firstLineBox()) return parent()->dirtyLinesFromChangedChild(this); // 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 = 0; RenderObject* curr = 0; for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) { if (curr->isFloatingOrPositioned()) continue; if (curr->isReplaced()) { InlineBox* wrapper = curr->inlineBoxWrapper(); if (wrapper) box = wrapper->root(); } else if (curr->isText()) { InlineTextBox* textBox = static_cast<RenderText*>(curr)->lastTextBox(); if (textBox) box = textBox->root(); } else if (curr->isInlineFlow()) { InlineRunBox* runBox = static_cast<RenderFlow*>(curr)->lastLineBox(); if (runBox) box = runBox->root(); } if (box) break; } if (!box && firstLineBox()) box = firstLineBox()->root(); // If we found a line box, then dirty it. if (box) { RootInlineBox* adjacentBox; 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." adjacentBox = box->prevRootBox(); if (adjacentBox) adjacentBox->markDirty(); if (child->isBR() || (curr && curr->isBR())) { adjacentBox = box->nextRootBox(); if (adjacentBox) adjacentBox->markDirty(); } } }
int InlineFlowBox::placeBoxesHorizontally(int x) { // Set our x position. setXPos(x); int startX = x; x += borderLeft() + paddingLeft(); for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isText()) { InlineTextBox* text = static_cast<InlineTextBox*>(curr); text->setXPos(x); x += curr->width(); } else { if (curr->object()->isPositioned()) { if (curr->object()->parent()->style()->direction() == LTR) curr->setXPos(x); else { // Our offset that we cache needs to be from the edge of the right border box and // not the left border box. We have to subtract |x| from the width of the block // (which can be obtained by walking up to the root line box). InlineBox* root = this; while (!root->isRootInlineBox()) root = root->parent(); curr->setXPos(root->object()->width()-x); } continue; // The positioned object has no effect on the width. } if (curr->object()->isInlineFlow()) { InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); x += flow->marginLeft(); x = flow->placeBoxesHorizontally(x); x += flow->marginRight(); } else { x += curr->object()->marginLeft(); curr->setXPos(x); x += curr->width() + curr->object()->marginRight(); } } } x += borderRight() + paddingRight(); setWidth(x-startX); return x; }
Position VisiblePosition::rightVisuallyDistinctCandidate() const { Position p = m_deepPosition; if (p.isNull()) return Position(); Position downstreamStart = p.downstream(); TextDirection primaryDirection = p.primaryDirection(); while (true) { InlineBox* box; int offset; p.getInlineBoxAndOffset(m_affinity, primaryDirection, box, offset); if (!box) return primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); RenderObject* renderer = box->renderer(); while (true) { if ((renderer->isReplaced() || renderer->isBR()) && offset == box->caretLeftmostOffset()) return box->isLeftToRightDirection() ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); if (!renderer->node()) { box = box->nextLeafChild(); if (!box) return primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); renderer = box->renderer(); offset = box->caretLeftmostOffset(); continue; } offset = box->isLeftToRightDirection() ? renderer->nextOffset(offset) : renderer->previousOffset(offset); int caretMinOffset = box->caretMinOffset(); int caretMaxOffset = box->caretMaxOffset(); if (offset > caretMinOffset && offset < caretMaxOffset) break; if (box->isLeftToRightDirection() ? offset > caretMaxOffset : offset < caretMinOffset) { // Overshot to the right. InlineBox* nextBox = box->nextLeafChildIgnoringLineBreak(); if (!nextBox) { Position positionOnRight = primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); if (positionOnRight.isNull()) return Position(); InlineBox* boxOnRight; int offsetOnRight; positionOnRight.getInlineBoxAndOffset(m_affinity, primaryDirection, boxOnRight, offsetOnRight); if (boxOnRight && boxOnRight->root() == box->root()) return Position(); return positionOnRight; } // Reposition at the other logical position corresponding to our edge's visual position and go for another round. box = nextBox; renderer = box->renderer(); offset = nextBox->caretLeftmostOffset(); continue; } ASSERT(offset == box->caretRightmostOffset()); unsigned char level = box->bidiLevel(); InlineBox* nextBox = box->nextLeafChild(); if (box->direction() == primaryDirection) { if (!nextBox) { InlineBox* logicalEnd = 0; if (primaryDirection == LTR ? box->root()->getLogicalEndBoxWithNode(logicalEnd) : box->root()->getLogicalStartBoxWithNode(logicalEnd)) { box = logicalEnd; renderer = box->renderer(); offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); } break; } if (nextBox->bidiLevel() >= level) break; level = nextBox->bidiLevel(); InlineBox* prevBox = box; do { prevBox = prevBox->prevLeafChild(); } while (prevBox && prevBox->bidiLevel() > level); if (prevBox && prevBox->bidiLevel() == level) // For example, abc FED 123 ^ CBA break; // For example, abc 123 ^ CBA or 123 ^ CBA abc box = nextBox; renderer = box->renderer(); offset = box->caretLeftmostOffset(); if (box->direction() == primaryDirection) break; continue; } while (nextBox && !nextBox->renderer()->node()) nextBox = nextBox->nextLeafChild(); if (nextBox) { box = nextBox; renderer = box->renderer(); offset = box->caretLeftmostOffset(); if (box->bidiLevel() > level) { do { nextBox = nextBox->nextLeafChild(); } while (nextBox && nextBox->bidiLevel() > level); if (!nextBox || nextBox->bidiLevel() < level) continue; } } else { // Trailing edge of a secondary run. Set to the leading edge of the entire run. while (true) { while (InlineBox* prevBox = box->prevLeafChild()) { if (prevBox->bidiLevel() < level) break; box = prevBox; } if (box->bidiLevel() == level) break; level = box->bidiLevel(); while (InlineBox* nextBox = box->nextLeafChild()) { if (nextBox->bidiLevel() < level) break; box = nextBox; } if (box->bidiLevel() == level) break; level = box->bidiLevel(); } renderer = box->renderer(); offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); } break; } p = createLegacyEditingPosition(renderer->node(), offset); if ((p.isCandidate() && p.downstream() != downstreamStart) || p.atStartOfTree() || p.atEndOfTree()) return p; ASSERT(p != m_deepPosition); } }
void InlineFlowBox::adjustMaxAscentAndDescent(int& maxAscent, int& maxDescent, int maxPositionTop, int maxPositionBottom) { for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { // The computed lineheight needs to be extended for the // positioned elements // see khtmltests/rendering/html_align.html if (curr->object()->isPositioned()) continue; // Positioned placeholders don't affect calculations. if (curr->yPos() == PositionTop || curr->yPos() == PositionBottom) { if (curr->yPos() == PositionTop) { if (maxAscent + maxDescent < curr->height()) maxDescent = curr->height() - maxAscent; } else { if (maxAscent + maxDescent < curr->height()) maxAscent = curr->height() - maxDescent; } if ( maxAscent + maxDescent >= kMax( maxPositionTop, maxPositionBottom ) ) break; } if (curr->isInlineFlowBox()) static_cast<InlineFlowBox*>(curr)->adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); } }
void LineBoxList::dirtyLinesFromChangedChild(LayoutObject* container, LayoutObject* child) { if (!container->parent() || (container->isLayoutBlock() && (container->selfNeedsLayout() || !container->isLayoutBlockFlow()))) return; LayoutInline* inlineContainer = container->isLayoutInline() ? toLayoutInline(container) : 0; InlineBox* firstBox = inlineContainer ? inlineContainer->firstLineBoxIncludingCulling() : firstLineBox(); // If we have no first line box, then just bail early. if (!firstBox) { // For an empty inline, go ahead and 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 = 0; LayoutObject* curr = 0; ListHashSet<LayoutObject*, 16> potentialLineBreakObjects; potentialLineBreakObjects.add(child); for (curr = child->previousSibling(); curr; curr = curr->previousSibling()) { potentialLineBreakObjects.add(curr); if (curr->isFloatingOrOutOfFlowPositioned()) continue; if (curr->isReplaced()) { InlineBox* wrapper = toLayoutBox(curr)->inlineBoxWrapper(); if (wrapper) box = &wrapper->root(); } else if (curr->isText()) { InlineTextBox* textBox = toLayoutText(curr)->lastTextBox(); if (textBox) box = &textBox->root(); } else if (curr->isLayoutInline()) { InlineBox* lastSiblingBox = toLayoutInline(curr)->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) { RootInlineBox* adjacentBox; 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 LayoutBlock::layoutInlineChildren // calls setLineBreakInfo with the result of findNextLineBreak. findNextLineBreak, // despite the name, actually returns the first LayoutObject after the BR. // <rdar://problem/3849947> "Typing after pasting line does not appear until after window resize." adjacentBox = box->prevRootBox(); if (adjacentBox) adjacentBox->markDirty(); adjacentBox = box->nextRootBox(); // If |child| or any of its immediately previous siblings with culled lineboxes is the object after a line-break in |box| or the linebox after it // then that means |child| actually sits on the linebox after |box| (or is its line-break object) and so we need to dirty it as well. if (adjacentBox && (potentialLineBreakObjects.contains(box->lineBreakObj()) || potentialLineBreakObjects.contains(adjacentBox->lineBreakObj()) || child->isBR() || isIsolated(container->style()->unicodeBidi()))) adjacentBox->markDirty(); } }
void InlineFlowBox::computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, int& maxAscent, int& maxDescent, bool strictMode) { if (isRootInlineBox()) { // Examine our root box. setHeight(object()->lineHeight(m_firstLine)); bool isTableCell = object()->isTableCell(); if (isTableCell) { RenderTableCell* tableCell = static_cast<RenderTableCell*>(object()); setBaseline(tableCell->RenderBlock::baselinePosition(m_firstLine)); } else setBaseline(object()->baselinePosition(m_firstLine)); if (hasTextChildren() || strictMode) { int ascent = baseline(); int descent = height() - ascent; if (maxAscent < ascent) maxAscent = ascent; if (maxDescent < descent) maxDescent = descent; } } for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isPositioned()) continue; // Positioned placeholders don't affect calculations. curr->setHeight(curr->object()->lineHeight(m_firstLine)); curr->setBaseline(curr->object()->baselinePosition(m_firstLine)); curr->setYPos(curr->object()->verticalPositionHint(m_firstLine)); if (curr->yPos() == PositionTop) { if (maxPositionTop < curr->height()) maxPositionTop = curr->height(); } else if (curr->yPos() == PositionBottom) { if (maxPositionBottom < curr->height()) maxPositionBottom = curr->height(); } else if (curr->hasTextChildren() || strictMode) { int ascent = curr->baseline() - curr->yPos(); int descent = curr->height() - ascent; if (maxAscent < ascent) maxAscent = ascent; if (maxDescent < descent) maxDescent = descent; } if (curr->isInlineFlowBox()) static_cast<InlineFlowBox*>(curr)->computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); } }
InlineBox* LayoutListMarker::createInlineBox() { InlineBox* result = LayoutBox::createInlineBox(); result->setIsText(isText()); return result; }
void InlineFlowBox::placeBoxesVertically(int y, int maxHeight, int maxAscent, bool strictMode, int& topPosition, int& bottomPosition) { if (isRootInlineBox()) { setYPos(y + maxAscent - baseline());// Place our root box. // CSS2: 10.8.1 - line-height on the block level element specifies the *minimum* // height of the generated line box if (hasTextChildren() && maxHeight < object()->lineHeight(m_firstLine)) maxHeight = object()->lineHeight(m_firstLine); } for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { if (curr->object()->isPositioned()) continue; // Positioned placeholders don't affect calculations. // Adjust boxes to use their real box y/height and not the logical height (as dictated by // line-height). if (curr->isInlineFlowBox()) static_cast<InlineFlowBox*>(curr)->placeBoxesVertically(y, maxHeight, maxAscent, strictMode, topPosition, bottomPosition); bool childAffectsTopBottomPos = true; if (curr->yPos() == PositionTop) curr->setYPos(y); else if (curr->yPos() == PositionBottom) curr->setYPos(y + maxHeight - curr->height()); else { if (!strictMode && !curr->hasTextDescendant()) childAffectsTopBottomPos = false; curr->setYPos(curr->yPos() + y + maxAscent - curr->baseline()); } int newY = curr->yPos(); int newHeight = curr->height(); int newBaseline = curr->baseline(); int overflowTop = 0; int overflowBottom = 0; if (curr->isInlineTextBox() || curr->isInlineFlowBox()) { const QFontMetrics &fm = curr->object()->fontMetrics( m_firstLine ); #ifdef APPLE_CHANGES newBaseline = fm.ascent(); newY += curr->baseline() - newBaseline; newHeight = newBaseline+fm.descent(); #else // only adjust if the leading delta is superior to the font's natural leading if ( kAbs(fm.ascent() - curr->baseline()) > fm.leading()/2 ) { int ascent = fm.ascent()+fm.leading()/2; newBaseline = ascent; newY += curr->baseline() - newBaseline; newHeight = fm.lineSpacing(); } #endif for (ShadowData* shadow = curr->object()->style()->textShadow(); shadow; shadow = shadow->next) { overflowTop = kMin(overflowTop, shadow->y - shadow->blur); overflowBottom = kMax(overflowBottom, shadow->y + shadow->blur); } if (curr->isInlineFlowBox()) { newHeight += curr->object()->borderTop() + curr->object()->paddingTop() + curr->object()->borderBottom() + curr->object()->paddingBottom(); newY -= curr->object()->borderTop() + curr->object()->paddingTop(); newBaseline += curr->object()->borderTop() + curr->object()->paddingTop(); } } else { newY += curr->object()->marginTop(); newHeight = curr->height() - (curr->object()->marginTop() + curr->object()->marginBottom()); overflowTop = curr->object()->overflowTop(); overflowBottom = curr->object()->overflowHeight() - newHeight; } curr->setYPos(newY); curr->setHeight(newHeight); curr->setBaseline(newBaseline); if (childAffectsTopBottomPos) { topPosition = kMin(topPosition, newY + overflowTop); bottomPosition = kMax(bottomPosition, newY + newHeight + overflowBottom); } } if (isRootInlineBox()) { const QFontMetrics &fm = object()->fontMetrics( m_firstLine ); #ifdef APPLE_CHANGES setHeight(fm.ascent()+fm.descent()); setYPos(yPos() + baseline() - fm.ascent()); setBaseline(fm.ascent()); #else if ( kAbs(fm.ascent() - baseline()) > fm.leading()/2 ) { int ascent = fm.ascent()+fm.leading()/2; setHeight(fm.lineSpacing()); setYPos(yPos() + baseline() - ascent); setBaseline(ascent); } #endif if (hasTextDescendant() || strictMode) { if (yPos() < topPosition) topPosition = yPos(); if (yPos() + height() > bottomPosition) bottomPosition = yPos() + height(); } } }
void SVGTextChunkLayoutInfo::recursiveBuildTextChunks(InlineFlowBox* start) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> buildTextChunks(start=%p)\n", start); #endif for (InlineBox* curr = start->firstChild(); curr; curr = curr->nextOnLine()) { if (curr->renderer()->isText()) { InlineTextBox* textBox = static_cast<InlineTextBox*>(curr); unsigned length = textBox->len(); ASSERT(length > 0); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Handle inline text box (%p) with %i characters (start: %i, end: %i), handlingTextPath=%i\n", textBox, length, textBox->start(), textBox->end(), (int) m_handlingTextPath); #endif RenderText* text = textBox->textRenderer(); ASSERT(text); ASSERT(text->node()); SVGTextContentElement* textContent = 0; Node* node = text->node()->parent(); while (node && node->isSVGElement() && !textContent) { if (static_cast<SVGElement*>(node)->isTextContent()) textContent = static_cast<SVGTextContentElement*>(node); else node = node->parentNode(); } ASSERT(textContent); // Start new character range for the first chunk bool isFirstCharacter = m_svgTextChunks.isEmpty() && m_chunk.start == m_charsIt && m_chunk.start == m_chunk.end; if (isFirstCharacter) { ASSERT(m_chunk.boxes.isEmpty()); m_chunk.boxes.append(SVGInlineBoxCharacterRange()); } else ASSERT(!m_chunk.boxes.isEmpty()); // Walk string to find out new chunk positions, if existent for (unsigned i = 0; i < length; ++i) { ASSERT(m_charsIt != m_charsEnd); SVGInlineBoxCharacterRange& range = m_chunk.boxes.last(); if (range.isOpen()) { range.box = curr; range.startOffset = !i ? 0 : i - 1; #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Range is open! box=%p, startOffset=%i\n", range.box, range.startOffset); #endif } // If a new (or the first) chunk has been started, record it's text-anchor and writing mode. if (m_assignChunkProperties) { m_assignChunkProperties = false; m_chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); m_chunk.isTextPath = m_handlingTextPath; m_chunk.anchor = text->style()->svgStyle()->textAnchor(); m_chunk.textLength = textContent->textLength().value(textContent); m_chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Assign chunk properties, isVerticalText=%i, anchor=%i\n", m_chunk.isVerticalText, m_chunk.anchor); #endif } if (i > 0 && !isFirstCharacter && m_charsIt->newTextChunk) { // Close mid chunk & character range ASSERT(!range.isOpen()); ASSERT(!range.isClosed()); range.endOffset = i; closeTextChunk(); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Close mid-text chunk, at endOffset: %i and starting new mid chunk!\n", range.endOffset); #endif // Prepare for next chunk, if we're not at the end startTextChunk(); if (i + 1 == length) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " | -> Record last chunk of inline text box!\n"); #endif startTextChunk(); SVGInlineBoxCharacterRange& range = m_chunk.boxes.last(); m_assignChunkProperties = false; m_chunk.isVerticalText = isVerticalWritingMode(text->style()->svgStyle()); m_chunk.isTextPath = m_handlingTextPath; m_chunk.anchor = text->style()->svgStyle()->textAnchor(); m_chunk.textLength = textContent->textLength().value(textContent); m_chunk.lengthAdjust = (ELengthAdjust) textContent->lengthAdjust(); range.box = curr; range.startOffset = i; ASSERT(!range.isOpen()); ASSERT(!range.isClosed()); } } // This should only hold true for the first character of the first chunk if (isFirstCharacter) isFirstCharacter = false; ++m_charsIt; } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Finished inline text box!\n"); #endif SVGInlineBoxCharacterRange& range = m_chunk.boxes.last(); if (!range.isOpen() && !range.isClosed()) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Last range not closed - closing with endOffset: %i\n", length); #endif // Current text chunk is not yet closed. Finish the current range, but don't start a new chunk. range.endOffset = length; if (m_charsIt != m_charsEnd) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Not at last character yet!\n"); #endif // If we're not at the end of the last box to be processed, and if the next // character starts a new chunk, then close the current chunk and start a new one. if (m_charsIt->newTextChunk) { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Next character starts new chunk! Closing current chunk, and starting a new one...\n"); #endif closeTextChunk(); startTextChunk(); } else { // Just start a new character range m_chunk.boxes.append(SVGInlineBoxCharacterRange()); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Next character does NOT start a new chunk! Starting new character range...\n"); #endif } } else { #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Closing final chunk! Finished processing!\n"); #endif // Close final chunk, once we're at the end of the last box closeTextChunk(); } } } else { ASSERT(curr->isInlineFlowBox()); InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(curr); // Skip generated content. if (!flowBox->renderer()->node()) continue; bool isTextPath = flowBox->renderer()->node()->hasTagName(SVGNames::textPathTag); #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " -> Handle inline flow box (%p), isTextPath=%i\n", flowBox, (int) isTextPath); #endif if (isTextPath) m_handlingTextPath = true; recursiveBuildTextChunks(flowBox); if (isTextPath) m_handlingTextPath = false; } } #if DEBUG_CHUNK_BUILDING > 1 fprintf(stderr, " <- buildTextChunks(start=%p)\n", start); #endif }
void InlineFlowBox::clearTruncation() { for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) box->clearTruncation(); }