Position Position::upstream(EStayInBlock stayInBlock) const { Position start = equivalentDeepPosition(); NodeImpl *startNode = start.node(); if (!startNode) return Position(); NodeImpl *block = startNode->enclosingBlockFlowOrTableElement(); Position lastVisible; PositionIterator it(start); for (; !it.atStart(); it.previous()) { NodeImpl *currentNode = it.current().node(); if (stayInBlock) { NodeImpl *currentBlock = currentNode->enclosingBlockFlowOrTableElement(); if (block != currentBlock) return it.next(); } RenderObject *renderer = currentNode->renderer(); if (!renderer) continue; if (renderer->style()->visibility() != VISIBLE) continue; lastVisible = it.current(); if (renderer->isReplaced() || renderer->isBR()) { if (it.current().offset() >= renderer->caretMaxOffset()) return Position(currentNode, renderer->caretMaxOffset()); else continue; } if (renderer->isText() && static_cast<RenderText *>(renderer)->firstTextBox()) { if (currentNode != startNode) return Position(currentNode, renderer->caretMaxOffset()); if (it.current().offset() < 0) continue; uint textOffset = it.current().offset(); RenderText *textRenderer = static_cast<RenderText *>(renderer); for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (textOffset > box->start() && textOffset <= box->start() + box->len()) return it.current(); else if (box != textRenderer->lastTextBox() && !box->nextOnLine() && textOffset == box->start() + box->len() + 1) return it.current(); } } } return lastVisible.isNotNull() ? lastVisible : *this; }
static VisiblePosition logicalEndPositionForLine(const VisiblePosition& c) { if (c.isNull()) return VisiblePosition(); RootInlineBox* rootBox = rootBoxForLine(c); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && !p.deprecatedEditingOffset()) return c; return VisiblePosition(); } InlineBox* logicalEndBox; Node* logicalEndNode; getLogicalEndBoxAndNode(rootBox, logicalEndBox, logicalEndNode); if (!logicalEndNode) return VisiblePosition(); int endOffset = 1; if (logicalEndNode->hasTagName(brTag)) endOffset = 0; else if (logicalEndBox->isInlineTextBox()) { InlineTextBox* endTextBox = static_cast<InlineTextBox*>(logicalEndBox); endOffset = endTextBox->start(); if (!endTextBox->isLineBreak()) endOffset += endTextBox->len(); } return VisiblePosition(logicalEndNode, endOffset, VP_UPSTREAM_IF_POSSIBLE); }
LayoutRect LayoutSVGInlineText::localCaretRect(InlineBox* box, int caretOffset, LayoutUnit*) { if (!box || !box->isInlineTextBox()) return LayoutRect(); InlineTextBox* textBox = toInlineTextBox(box); if (static_cast<unsigned>(caretOffset) < textBox->start() || static_cast<unsigned>(caretOffset) > textBox->start() + textBox->len()) return LayoutRect(); // Use the edge of the selection rect to determine the caret rect. if (static_cast<unsigned>(caretOffset) < textBox->start() + textBox->len()) { LayoutRect rect = textBox->localSelectionRect(caretOffset, caretOffset + 1); LayoutUnit x = box->isLeftToRightDirection() ? rect.x() : rect.maxX(); return LayoutRect(x, rect.y(), caretWidth, rect.height()); } LayoutRect rect = textBox->localSelectionRect(caretOffset - 1, caretOffset); LayoutUnit x = box->isLeftToRightDirection() ? rect.maxX() : rect.x(); return LayoutRect(x, rect.y(), caretWidth, rect.height()); }
static IntRect ellipsisRectForBox(const InlineTextBox& box, unsigned start, unsigned end) { unsigned truncation = box.truncation(); if (truncation == cNoTruncation) return IntRect(); auto ellipsis = box.root().ellipsisBox(); if (!ellipsis) return IntRect(); IntRect rect; unsigned ellipsisStartPosition = start > box.start() ? start - box.start() : 0; ASSERT(end >= box.start()); unsigned 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(); }
static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) { // FIXME: Table cell adjustment is temporary until results can be updated. int y = run.m_y; if (o.containingBlock()->isTableCell()) y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingTop(); ts << "text run at (" << run.m_x << "," << y << ") width " << run.m_width; if (run.direction() == RTL || run.m_dirOverride) { ts << (run.direction() == RTL ? " RTL" : " LTR"); if (run.m_dirOverride) ts << " override"; } ts << ": " << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())) << "\n"; }
static VisiblePosition endPositionForLine(const VisiblePosition& c) { if (c.isNull()) return VisiblePosition(); RootInlineBox *rootBox = rootBoxForLine(c); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0) return c; return VisiblePosition(); } // Generated content (e.g. list markers and CSS :before and :after // pseudoelements) have no corresponding DOM element, and so cannot be // represented by a VisiblePosition. Use whatever precedes instead. Node *endNode; InlineBox *endBox = rootBox->lastLeafChild(); while (1) { if (!endBox) return VisiblePosition(); RenderObject *endRenderer = endBox->renderer(); if (!endRenderer) return VisiblePosition(); endNode = endRenderer->node(); if (endNode) break; endBox = endBox->prevLeafChild(); } int endOffset = 1; if (endNode->hasTagName(brTag)) { endOffset = 0; } else if (endBox->isInlineTextBox()) { InlineTextBox *endTextBox = static_cast<InlineTextBox *>(endBox); endOffset = endTextBox->start(); if (!endTextBox->isLineBreak()) endOffset += endTextBox->len(); } return VisiblePosition(endNode, endOffset, VP_UPSTREAM_IF_POSSIBLE); }
static VisiblePosition startPositionForLine(const VisiblePosition& c) { if (c.isNull()) return VisiblePosition(); RootInlineBox *rootBox = rootBoxForLine(c); if (!rootBox) { // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.deprecatedEditingOffset() == 0) return positionAvoidingFirstPositionInTable(c); return VisiblePosition(); } // Generated content (e.g. list markers and CSS :before and :after // pseudoelements) have no corresponding DOM element, and so cannot be // represented by a VisiblePosition. Use whatever follows instead. InlineBox *startBox = rootBox->firstLeafChild(); Node *startNode; while (1) { if (!startBox) return VisiblePosition(); RenderObject *startRenderer = startBox->renderer(); if (!startRenderer) return VisiblePosition(); startNode = startRenderer->node(); if (startNode) break; startBox = startBox->nextLeafChild(); } int startOffset = 0; if (startBox->isInlineTextBox()) { InlineTextBox *startTextBox = static_cast<InlineTextBox *>(startBox); startOffset = startTextBox->start(); } VisiblePosition visPos = VisiblePosition(startNode, startOffset, DOWNSTREAM); return positionAvoidingFirstPositionInTable(visPos); }
static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) { // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder // to detect any changes caused by the conversion to floating point. :( int x = run.x(); int y = run.y(); int logicalWidth = ceilf(run.left() + run.logicalWidth()) - x; ts << "text run at (" << x << "," << y << ") width " << logicalWidth; if (!run.isLeftToRightDirection() || run.dirOverride()) { ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR"); if (run.dirOverride()) ts << " override"; } ts << ": " << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())); if (run.hasHyphen()) ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString()); ts << "\n"; }
static void writeTextRun(TextStream& ts, const RenderText& o, const InlineTextBox& run) { // FIXME: For now use an "enclosingIntRect" model for x, y and logicalWidth, although this makes it harder // to detect any changes caused by the conversion to floating point. :( int x = run.m_x; int y = run.m_y; int logicalWidth = ceilf(run.m_x + run.m_logicalWidth) - x; // FIXME: Table cell adjustment is temporary until results can be updated. if (o.containingBlock()->isTableCell()) y -= toRenderTableCell(o.containingBlock())->intrinsicPaddingBefore(); ts << "text run at (" << x << "," << y << ") width " << logicalWidth; if (!run.isLeftToRightDirection() || run.m_dirOverride) { ts << (!run.isLeftToRightDirection() ? " RTL" : " LTR"); if (run.m_dirOverride) ts << " override"; } ts << ": " << quoteAndEscapeNonPrintables(String(o.text()).substring(run.start(), run.len())); if (run.hasHyphen()) ts << " + hyphen string " << quoteAndEscapeNonPrintables(o.style()->hyphenString()); ts << "\n"; }
// P.downstream() returns the end of the range of positions that map to the same VisiblePosition as P. Position Position::downstream() const { Node* startNode = node(); if (!startNode) return Position(); // iterate forward from there, looking for a qualified position Node* block = enclosingBlock(startNode); PositionIterator lastVisible = *this; PositionIterator currentPos = lastVisible; Node* originalRoot = node()->rootEditableElement(); for (; !currentPos.atEnd(); currentPos.increment()) { Node* currentNode = currentPos.node(); if (currentNode->rootEditableElement() != originalRoot) break; // stop before going above the body, up into the head // return the last visible streamer position if (currentNode->hasTagName(bodyTag) && currentPos.atEndOfNode()) break; // Do not enter a new enclosing block flow or table element, and don't leave the original one. if (block != enclosingBlock(currentNode)) return lastVisible; // skip position in unrendered or invisible node RenderObject* renderer = currentNode->renderer(); if (!renderer || renderer->style()->visibility() != VISIBLE) continue; // track last visible streamer position if (isStreamer(currentPos)) lastVisible = currentPos; // Return position before brs, tables, and nodes which have content that can be ignored. if (editingIgnoresContent(currentNode) || renderer->isBR() || isTableElement(currentNode)) { if (currentPos.offsetInLeafNode() <= renderer->caretMinOffset()) return Position(currentNode, renderer->caretMinOffset()); continue; } // return current position if it is in rendered text if (renderer->isText() && static_cast<RenderText*>(renderer)->firstTextBox()) { if (currentNode != startNode) { ASSERT(currentPos.atStartOfNode()); return Position(currentNode, renderer->caretMinOffset()); } unsigned textOffset = currentPos.offsetInLeafNode(); RenderText* textRenderer = static_cast<RenderText*>(renderer); for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (textOffset >= box->start() && textOffset <= box->end()) return currentPos; if (box != textRenderer->lastTextBox() && !box->nextOnLine() && textOffset == box->start() + box->len()) { return currentPos; } } } } return lastVisible; }
static VisiblePosition createVisiblePositionAfterAdjustingOffsetForBiDi(const InlineTextBox& box, int offset, ShouldAffinityBeDownstream shouldAffinityBeDownstream) { ASSERT(offset >= 0); if (offset && static_cast<unsigned>(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 RenderNamedFlowThread::getRanges(Vector<RefPtr<Range> >& rangeObjects, const RenderRegion* region) const { LayoutUnit logicalTopForRegion; LayoutUnit logicalBottomForRegion; // extend the first region top to contain everything up to its logical height if (region->isFirstRegion()) logicalTopForRegion = LayoutUnit::min(); else logicalTopForRegion = region->logicalTopForFlowThreadContent(); // extend the last region to contain everything above its y() if (region->isLastRegion()) logicalBottomForRegion = LayoutUnit::max(); else logicalBottomForRegion = region->logicalBottomForFlowThreadContent(); Vector<Node*> nodes; // eliminate the contentNodes that are descendants of other contentNodes for (NamedFlowContentNodes::const_iterator it = contentNodes().begin(); it != contentNodes().end(); ++it) { Node* node = *it; if (!isContainedInNodes(nodes, node)) nodes.append(node); } for (size_t i = 0; i < nodes.size(); i++) { Node* contentNode = nodes.at(i); if (!contentNode->renderer()) continue; RefPtr<Range> range = Range::create(contentNode->document()); bool foundStartPosition = false; bool startsAboveRegion = true; bool endsBelowRegion = true; bool skipOverOutsideNodes = false; Node* lastEndNode = 0; for (Node* node = contentNode; node; node = nextNodeInsideContentNode(node, contentNode)) { RenderObject* renderer = node->renderer(); if (!renderer) continue; LayoutRect boundingBox; if (renderer->isRenderInline()) boundingBox = toRenderInline(renderer)->linesBoundingBox(); else if (renderer->isText()) boundingBox = toRenderText(renderer)->linesBoundingBox(); else { boundingBox = toRenderBox(renderer)->frameRect(); if (toRenderBox(renderer)->isRelPositioned()) boundingBox.move(toRenderBox(renderer)->relativePositionLogicalOffset()); } LayoutUnit offsetTop = renderer->containingBlock()->offsetFromLogicalTopOfFirstPage(); const LayoutPoint logicalOffsetFromTop(isHorizontalWritingMode() ? LayoutUnit() : offsetTop, isHorizontalWritingMode() ? offsetTop : LayoutUnit()); boundingBox.moveBy(logicalOffsetFromTop); LayoutUnit logicalTopForRenderer = region->logicalTopOfFlowThreadContentRect(boundingBox); LayoutUnit logicalBottomForRenderer = region->logicalBottomOfFlowThreadContentRect(boundingBox); // if the bounding box of the current element doesn't intersect the region box // close the current range only if the start element began inside the region, // otherwise just move the start position after this node and keep skipping them until we found a proper start position. if (!boxIntersectsRegion(logicalTopForRenderer, logicalBottomForRenderer, logicalTopForRegion, logicalBottomForRegion)) { if (foundStartPosition) { if (!startsAboveRegion) { if (range->intersectsNode(node, IGNORE_EXCEPTION)) range->setEndBefore(node, IGNORE_EXCEPTION); rangeObjects.append(range->cloneRange(IGNORE_EXCEPTION)); range = Range::create(contentNode->document()); startsAboveRegion = true; } else skipOverOutsideNodes = true; } if (skipOverOutsideNodes) range->setStartAfter(node, IGNORE_EXCEPTION); foundStartPosition = false; continue; } // start position if (logicalTopForRenderer < logicalTopForRegion && startsAboveRegion) { if (renderer->isText()) { // Text crosses region top // for Text elements, just find the last textbox that is contained inside the region and use its start() offset as start position RenderText* textRenderer = toRenderText(renderer); for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (offsetTop + box->logicalBottom() < logicalTopForRegion) continue; range->setStart(Position(toText(node), box->start())); startsAboveRegion = false; break; } } else { // node crosses region top // for all elements, except Text, just set the start position to be before their children startsAboveRegion = true; range->setStart(Position(node, Position::PositionIsBeforeChildren)); } } else { // node starts inside region // for elements that start inside the region, set the start position to be before them. If we found one, we will just skip the others until // the range is closed. if (startsAboveRegion) { startsAboveRegion = false; range->setStartBefore(node, IGNORE_EXCEPTION); } } skipOverOutsideNodes = false; foundStartPosition = true; // end position if (logicalBottomForRegion < logicalBottomForRenderer && (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode)))) { // for Text elements, just find just find the last textbox that is contained inside the region and use its start()+len() offset as end position if (renderer->isText()) { // Text crosses region bottom RenderText* textRenderer = toRenderText(renderer); InlineTextBox* lastBox = 0; for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if ((offsetTop + box->logicalTop()) < logicalBottomForRegion) { lastBox = box; continue; } ASSERT(lastBox); if (lastBox) range->setEnd(Position(toText(node), lastBox->start() + lastBox->len())); break; } endsBelowRegion = false; lastEndNode = node; } else { // node crosses region bottom // for all elements, except Text, just set the start position to be after their children range->setEnd(Position(node, Position::PositionIsAfterChildren)); endsBelowRegion = true; lastEndNode = node; } } else { // node ends inside region // for elements that ends inside the region, set the end position to be after them // allow this end position to be changed only by other elements that are not descendants of the current end node if (endsBelowRegion || (!endsBelowRegion && !node->isDescendantOf(lastEndNode))) { range->setEndAfter(node, IGNORE_EXCEPTION); endsBelowRegion = false; lastEndNode = node; } } } if (foundStartPosition || skipOverOutsideNodes) rangeObjects.append(range); } }
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; }
Position Position::downstream(EStayInBlock stayInBlock) const { Position start = equivalentDeepPosition(); NodeImpl *startNode = start.node(); if (!startNode) return Position(); NodeImpl *block = startNode->enclosingBlockFlowOrTableElement(); Position lastVisible; PositionIterator it(start); for (; !it.atEnd(); it.next()) { NodeImpl *currentNode = it.current().node(); if (stayInBlock) { NodeImpl *currentBlock = currentNode->enclosingBlockFlowOrTableElement(); if (block != currentBlock) return it.previous(); } RenderObject *renderer = currentNode->renderer(); if (!renderer) continue; if (renderer->style()->visibility() != VISIBLE) continue; lastVisible = it.current(); if (currentNode != startNode && renderer->isBlockFlow()) { if (it.current().offset() == 0) { // If no first child, or first visible child is a not a block, return; otherwise continue. if (!currentNode->firstChild()) return Position(currentNode, 0); for (NodeImpl *child = currentNode->firstChild(); child; child = child->nextSibling()) { RenderObject *r = child->renderer(); if (r && r->style()->visibility() == VISIBLE) { if (r->isBlockFlow()) break; // break causes continue code below to run. else return Position(child, 0); } } continue; } } if (renderer->isReplaced() || renderer->isBR()) { if (it.current().offset() <= renderer->caretMinOffset()) return Position(currentNode, renderer->caretMinOffset()); else continue; } if (renderer->isText() && static_cast<RenderText *>(renderer)->firstTextBox()) { if (currentNode != start.node()) return Position(currentNode, renderer->caretMinOffset()); if (it.current().offset() < 0) continue; uint textOffset = it.current().offset(); RenderText *textRenderer = static_cast<RenderText *>(renderer); for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (textOffset >= box->start() && textOffset <= box->end()) return it.current(); else if (box != textRenderer->lastTextBox() && !box->nextOnLine() && textOffset == box->start() + box->len()) return it.current(); } } } return lastVisible.isNotNull() ? lastVisible : *this; }
static gchar* textForRenderer(RenderObject* renderer) { GString* resultText = g_string_new(0); if (!renderer) return g_string_free(resultText, FALSE); // For RenderBlocks, piece together the text from the RenderText objects they contain. for (RenderObject* object = renderer->firstChild(); object; object = object->nextSibling()) { if (object->isBR()) { g_string_append(resultText, "\n"); continue; } RenderText* renderText; if (object->isText()) renderText = toRenderText(object); else { // List item's markers will be treated in an special way // later on this function, so ignore them here. if (object->isReplaced() && !renderer->isListItem()) g_string_append_unichar(resultText, objectReplacementCharacter); // We need to check children, if any, to consider when // current object is not a text object but some of its // children are, in order not to miss those portions of // text by not properly handling those situations if (object->firstChild()) g_string_append(resultText, textForRenderer(object)); continue; } InlineTextBox* box = renderText ? renderText->firstTextBox() : 0; while (box) { // WebCore introduces line breaks in the text that do not reflect // the layout you see on the screen, replace them with spaces. String text = String(renderText->characters(), renderText->textLength()).replace("\n", " "); g_string_append(resultText, text.substring(box->start(), box->end() - box->start() + 1).utf8().data()); // Newline chars in the source result in separate text boxes, so check // before adding a newline in the layout. See bug 25415 comment #78. // If the next sibling is a BR, we'll add the newline when we examine that child. if (!box->nextOnLineExists() && !(object->nextSibling() && object->nextSibling()->isBR())) { // If there was a '\n' in the last position of the // current text box, it would have been converted to a // space in String::replace(), so remove it first. if (renderText->characters()[box->end()] == '\n') g_string_erase(resultText, resultText->len - 1, -1); g_string_append(resultText, "\n"); } box = box->nextTextBox(); } } // Insert the text of the marker for list item in the right place, if present if (renderer->isListItem()) { String markerText = toRenderListItem(renderer)->markerTextWithSuffix(); if (renderer->style()->direction() == LTR) g_string_prepend(resultText, markerText.utf8().data()); else g_string_append(resultText, markerText.utf8().data()); } return g_string_free(resultText, FALSE); }
// p.upstream() returns the start of the range of positions that map to the same VisiblePosition as P. Position Position::upstream() const { Node* startNode = node(); if (!startNode) return Position(); // iterate backward from there, looking for a qualified position Node* block = enclosingBlock(startNode); PositionIterator lastVisible = *this; PositionIterator currentPos = lastVisible; Node* originalRoot = node()->rootEditableElement(); for (; !currentPos.atStart(); currentPos.decrement()) { Node* currentNode = currentPos.node(); if (currentNode->rootEditableElement() != originalRoot) break; // Don't enter a new enclosing block flow or table element. There is code below that // terminates early if we're about to leave an enclosing block flow or table element. if (block != enclosingBlock(currentNode)) return lastVisible; // skip position in unrendered or invisible node RenderObject* renderer = currentNode->renderer(); if (!renderer || renderer->style()->visibility() != VISIBLE) continue; // track last visible streamer position if (isStreamer(currentPos)) lastVisible = currentPos; // Don't leave a block flow or table element. We could rely on code above to terminate and // return lastVisible on the next iteration, but we terminate early. if (currentNode == enclosingBlock(currentNode) && currentPos.atStartOfNode()) return lastVisible; // Return position after brs, tables, and nodes which have content that can be ignored. if (editingIgnoresContent(currentNode) || renderer->isBR() || isTableElement(currentNode)) { if (currentPos.atEndOfNode()) return Position(currentNode, maxDeepOffset(currentNode)); continue; } // return current position if it is in rendered text if (renderer->isText() && static_cast<RenderText*>(renderer)->firstTextBox()) { if (currentNode != startNode) { // This assertion fires in layout tests in the case-transform.html test because // of a mix-up between offsets in the text in the DOM tree with text in the // render tree which can have a different length due to case transformation. // Until we resolve that, disable this so we can run the layout tests! //ASSERT(currentOffset >= renderer->caretMaxOffset()); return Position(currentNode, renderer->caretMaxOffset()); } unsigned textOffset = currentPos.offsetInLeafNode(); RenderText* textRenderer = static_cast<RenderText*>(renderer); for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) { if (textOffset > box->start() && textOffset <= box->start() + box->len()) return currentPos; if (box != textRenderer->lastTextBox() && !box->nextOnLine() && textOffset == box->start() + box->len() + 1) return currentPos; } } } return lastVisible; }
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 }
static PangoLayout* getPangoLayoutForAtk(AtkText* textObject) { AccessibilityObject* coreObject = core(textObject); HostWindow* hostWindow = coreObject->document()->view()->hostWindow(); if (!hostWindow) return 0; PlatformPageClient webView = hostWindow->platformPageClient(); if (!webView) return 0; GString* str = g_string_new(NULL); AccessibilityRenderObject* accObject = static_cast<AccessibilityRenderObject*>(coreObject); if (!accObject) return 0; RenderText* renderText = toRenderText(accObject->renderer()); if (!renderText) return 0; // Create a string with the layout as it appears on the screen InlineTextBox* box = renderText->firstTextBox(); while (box) { gchar *text = convertUniCharToUTF8(renderText->characters(), renderText->textLength(), box->start(), box->end()); g_string_append(str, text); g_string_append(str, "\n"); box = box->nextTextBox(); } PangoLayout* layout = gtk_widget_create_pango_layout(static_cast<GtkWidget*>(webView), g_string_free(str, FALSE)); g_object_set_data_full(G_OBJECT(textObject), "webkit-accessible-pango-layout", layout, g_object_unref); return layout; }