bool SVGInlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, result.hitTestRequest(), lineLayoutItem().style()->pointerEvents()); bool isVisible = lineLayoutItem().style()->visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if (hitRules.canHitBoundingBox || (hitRules.canHitStroke && (lineLayoutItem().style()->svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (lineLayoutItem().style()->svgStyle().hasFill() || !hitRules.requireFill))) { LayoutPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); LayoutRect rect(boxOrigin, size()); if (locationInContainer.intersects(rect)) { LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->lineLayoutItem()); ASSERT(lineLayoutItem.scalingFactor()); float baseline = lineLayoutItem.scaledFont().fontMetrics().floatAscent() / lineLayoutItem.scalingFactor(); FloatPoint floatLocation = FloatPoint(locationInContainer.point()); for (const SVGTextFragment& fragment : m_textFragments) { FloatQuad fragmentQuad = fragment.boundingQuad(baseline); if (fragmentQuad.containsPoint(floatLocation)) { lineLayoutItem.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToListBasedTestResult(lineLayoutItem.node(), locationInContainer, rect)) return true; } } } } } return false; }
int InlineTextBox::baselinePosition(FontBaseline baselineType) const { if (!isText() || !parent()) return 0; if (parent()->lineLayoutItem() == lineLayoutItem().parent()) return parent()->baselinePosition(baselineType); return LineLayoutBoxModel(lineLayoutItem().parent()).baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); }
LayoutUnit InlineTextBox::lineHeight() const { if (!isText() || !lineLayoutItem().parent()) return 0; if (lineLayoutItem().isBR()) return toLayoutBR(lineLayoutItem())->lineHeight(isFirstLineStyle()); if (parent()->lineLayoutItem() == lineLayoutItem().parent()) return parent()->lineHeight(); return LineLayoutBoxModel(lineLayoutItem().parent()).lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); }
TextRun InlineTextBox::constructTextRun(const ComputedStyle& style, const Font& font, StringBuilder* charactersWithHyphen) const { ASSERT(lineLayoutItem().text()); StringView string = lineLayoutItem().text().createView(); unsigned startPos = start(); unsigned length = len(); if (string.length() != length || startPos) string.narrow(startPos, length); return constructTextRun(style, font, string, lineLayoutItem().textLength() - startPos, charactersWithHyphen); }
void InlineTextBox::extractLine() { if (extracted()) return; lineLayoutItem().extractTextBox(this); }
void InlineTextBox::attachLine() { if (!extracted()) return; lineLayoutItem().attachTextBox(this); }
TextRun SVGInlineTextBox::constructTextRun(const ComputedStyle& style, const SVGTextFragment& fragment) const { LineLayoutText text = lineLayoutItem(); // FIXME(crbug.com/264211): This should not be necessary but can occur if we // layout during layout. Remove this when 264211 is fixed. RELEASE_ASSERT(!text.needsLayout()); TextRun run(static_cast<const LChar*>(nullptr) // characters, will be set below if non-zero. , 0 // length, will be set below if non-zero. , 0 // xPos, only relevant with allowTabs=true , 0 // padding, only relevant for justified text, not relevant for SVG , TextRun::AllowTrailingExpansion , direction() , dirOverride() || style.rtlOrdering() == VisualOrder /* directionalOverride */); if (fragment.length) { if (text.is8Bit()) run.setText(text.characters8() + fragment.characterOffset, fragment.length); else run.setText(text.characters16() + fragment.characterOffset, fragment.length); } // We handle letter & word spacing ourselves. run.disableSpacing(); // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring. run.setCharactersLength(text.textLength() - fragment.characterOffset); ASSERT(run.charactersLength() >= run.length()); return run; }
void InlineBox::move(const LayoutSize& delta) { m_topLeft.move(delta); if (lineLayoutItem().isReplaced()) toLayoutBox(layoutObject()).move(delta.width(), delta.height()); }
LayoutRect SVGInlineTextBox::localSelectionRect(int startPosition, int endPosition) const { int boxStart = start(); startPosition = std::max(startPosition - boxStart, 0); endPosition = std::min(endPosition - boxStart, static_cast<int>(len())); if (startPosition >= endPosition) return LayoutRect(); const ComputedStyle& style = lineLayoutItem().styleRef(); FloatRect selectionRect; int fragmentStartPosition = 0; int fragmentEndPosition = 0; unsigned textFragmentsSize = m_textFragments.size(); for (unsigned i = 0; i < textFragmentsSize; ++i) { const SVGTextFragment& fragment = m_textFragments.at(i); fragmentStartPosition = startPosition; fragmentEndPosition = endPosition; if (!mapStartEndPositionsIntoFragmentCoordinates(fragment, fragmentStartPosition, fragmentEndPosition)) continue; FloatRect fragmentRect = selectionRectForTextFragment(fragment, fragmentStartPosition, fragmentEndPosition, style); if (fragment.isTransformed()) fragmentRect = fragment.buildFragmentTransform().mapRect(fragmentRect); selectionRect.unite(fragmentRect); } return LayoutRect(enclosingIntRect(selectionRect)); }
LayoutRect InlineTextBox::localSelectionRect(int startPos, int endPos) const { int sPos = std::max(startPos - m_start, 0); int ePos = std::min(endPos - m_start, (int)m_len); if (sPos > ePos) return LayoutRect(); FontCachePurgePreventer fontCachePurgePreventer; LayoutUnit selTop = root().selectionTop(); LayoutUnit selHeight = root().selectionHeight(); const ComputedStyle& styleToUse = lineLayoutItem().styleRef(isFirstLineStyle()); const Font& font = styleToUse.font(); StringBuilder charactersWithHyphen; bool respectHyphen = ePos == m_len && hasHyphen(); TextRun textRun = constructTextRun(styleToUse, font, respectHyphen ? &charactersWithHyphen : 0); LayoutPoint startingPoint = LayoutPoint(logicalLeft(), selTop); LayoutRect r; if (sPos || ePos != static_cast<int>(m_len)) { r = LayoutRect(enclosingIntRect(font.selectionRectForText(textRun, FloatPoint(startingPoint), selHeight, sPos, ePos))); } else { // Avoid computing the font width when the entire line box is selected as an optimization. // FIXME: the call to rawValue() below is temporary and should be removed once the transition // to LayoutUnit-based types is complete (crbug.com/321237) r = LayoutRect(enclosingIntRect(LayoutRect(startingPoint, LayoutSize(m_logicalWidth, selHeight)))); } LayoutUnit logicalWidth = r.width(); if (r.x() > logicalRight()) logicalWidth = 0; else if (r.maxX() > logicalRight()) logicalWidth = logicalRight() - r.x(); LayoutPoint topPoint; LayoutUnit width; LayoutUnit height; if (isHorizontal()) { topPoint = LayoutPoint(r.x(), selTop); width = logicalWidth; height = selHeight; if (hasWrappedSelectionNewline()) { if (!isLeftToRightDirection()) topPoint.setX(topPoint.x() - newlineSpaceWidth()); width += newlineSpaceWidth(); } } else { topPoint = LayoutPoint(selTop, r.x()); width = selHeight; height = logicalWidth; // TODO(wkorman): RTL text embedded in top-to-bottom text can create // bottom-to-top situations. Add tests and ensure we handle correctly. if (hasWrappedSelectionNewline()) height += newlineSpaceWidth(); } return LayoutRect(topPoint, LayoutSize(width, height)); }
bool EllipsisBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit lineTop, LayoutUnit lineBottom) { // FIXME: the call to roundedLayoutPoint() below is temporary and should be removed once // the transition to LayoutUnit-based types is complete (crbug.com/321237) LayoutPoint adjustedLocation = accumulatedOffset + topLeft(); LayoutPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); LayoutRect boundsRect(boxOrigin, size()); if (visibleToHitTestRequest(result.hitTestRequest()) && boundsRect.intersects(LayoutRect(HitTestLocation::rectForPoint(locationInContainer.point(), 0, 0, 0, 0)))) { lineLayoutItem().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(adjustedLocation)); if (result.addNodeToListBasedTestResult(lineLayoutItem().node(), locationInContainer, boundsRect) == StopHitTesting) return true; } return false; }
SelectionState InlineTextBox::selectionState() const { SelectionState state = lineLayoutItem().selectionState(); if (state == SelectionStart || state == SelectionEnd || state == SelectionBoth) { int startPos, endPos; lineLayoutItem().selectionStartEnd(startPos, endPos); // The position after a hard line break is considered to be past its end. // See the corresponding code in InlineTextBox::isSelected. int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); // FIXME: Remove -webkit-line-break: LineBreakAfterWhiteSpace. int endOfLineAdjustmentForCSSLineBreak = lineLayoutItem().style()->lineBreak() == LineBreakAfterWhiteSpace ? -1 : 0; bool start = (state != SelectionEnd && startPos >= m_start && startPos <= m_start + m_len + endOfLineAdjustmentForCSSLineBreak); bool end = (state != SelectionStart && endPos > m_start && endPos <= lastSelectable); if (start && end) state = SelectionBoth; else if (start) state = SelectionStart; else if (end) state = SelectionEnd; else if ((state == SelectionEnd || startPos < m_start) && (state == SelectionStart || endPos > lastSelectable)) state = SelectionInside; else if (state == SelectionBoth) state = SelectionNone; } // If there are ellipsis following, make sure their selection is updated. if (m_truncation != cNoTruncation && root().ellipsisBox()) { EllipsisBox* ellipsis = root().ellipsisBox(); if (state != SelectionNone) { int start, end; selectionStartEnd(start, end); // 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. ellipsis->setSelectionState(end >= m_truncation && start <= m_truncation ? SelectionInside : SelectionNone); } else { ellipsis->setSelectionState(SelectionNone); } } return state; }
bool InlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) { if (isLineBreak()) return false; LayoutPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); LayoutRect rect(boxOrigin, size()); // FIXME: both calls to rawValue() below is temporary and should be removed once the transition // to LayoutUnit-based types is complete (crbug.com/321237) if (m_truncation != cFullTruncation && visibleToHitTestRequest(result.hitTestRequest()) && locationInContainer.intersects(rect)) { lineLayoutItem().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); if (!result.addNodeToListBasedTestResult(lineLayoutItem().node(), locationInContainer, rect)) return true; } return false; }
LayoutUnit InlineBox::logicalHeight() const { if (hasVirtualLogicalHeight()) return virtualLogicalHeight(); if (lineLayoutItem().isText()) return m_bitfields.isText() ? LayoutUnit(lineLayoutItem().style(isFirstLineStyle())->fontMetrics().height()) : LayoutUnit(); if (lineLayoutItem().isBox() && parent()) return isHorizontal() ? toLayoutBox(layoutObject()).size().height() : toLayoutBox(layoutObject()).size().width(); ASSERT(isInlineFlowBox()); LineLayoutBoxModel flowObject = boxModelObject(); const FontMetrics& fontMetrics = lineLayoutItem().style(isFirstLineStyle())->fontMetrics(); LayoutUnit result = fontMetrics.height(); if (parent()) result += flowObject.borderAndPaddingLogicalHeight(); return result; }
bool InlineBox::canAccommodateEllipsis(bool ltr, int blockEdge, int ellipsisWidth) const { // Non-replaced elements can always accommodate an ellipsis. if (!lineLayoutItem().isReplaced()) return true; IntRect boxRect(left(), 0, m_logicalWidth, 10); IntRect ellipsisRect(ltr ? blockEdge - ellipsisWidth : blockEdge, 0, ellipsisWidth, 10); return !(boxRect.intersects(ellipsisRect)); }
bool InlineBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /* lineBottom */) { // Hit test all phases of replaced elements atomically, as though the replaced element established its // own stacking context. (See Appendix E.2, section 6.4 on inline block/table elements in the CSS2.1 // specification.) LayoutPoint childPoint = accumulatedOffset; if (parent()->lineLayoutItem().hasFlippedBlocksWritingMode()) // Faster than calling containingBlock(). childPoint = layoutObject().containingBlock()->flipForWritingModeForChild(&toLayoutBox(layoutObject()), childPoint); if (lineLayoutItem().style()->hasBorderRadius()) { LayoutRect borderRect = logicalFrameRect(); borderRect.moveBy(accumulatedOffset); FloatRoundedRect border = lineLayoutItem().style()->getRoundedBorderFor(borderRect); if (!locationInContainer.intersects(border)) return false; } return lineLayoutItem().hitTest(result, locationInContainer, childPoint); }
void InlineBox::logicalRectToPhysicalRect(LayoutRect& current) const { if (isHorizontal() && !lineLayoutItem().hasFlippedBlocksWritingMode()) return; if (!isHorizontal()) { current = current.transposedRect(); } current.setLocation(logicalPositionToPhysicalPoint(current.location(), current.size())); return; }
LayoutPoint InlineBox::logicalPositionToPhysicalPoint(const LayoutPoint& point, const LayoutSize& size) const { if (!UNLIKELY(lineLayoutItem().hasFlippedBlocksWritingMode())) return LayoutPoint(point.x(), point.y()); LayoutBlockFlow& block = root().block(); if (block.style()->isHorizontalWritingMode()) return LayoutPoint(point.x(), block.size().height() - size.height() - point.y()); return LayoutPoint(block.size().width() - size.width() - point.x(), point.y()); }
int InlineTextBox::offsetForPosition(LayoutUnit lineOffset, bool includePartialGlyphs) const { if (isLineBreak()) return 0; if (lineOffset - logicalLeft() > logicalWidth()) return isLeftToRightDirection() ? len() : 0; if (lineOffset - logicalLeft() < 0) return isLeftToRightDirection() ? 0 : len(); LineLayoutText text = lineLayoutItem(); const ComputedStyle& style = text.styleRef(isFirstLineStyle()); const Font& font = style.font(); return font.offsetForPosition(constructTextRun(style, font), (lineOffset - logicalLeft()).toFloat(), includePartialGlyphs); }
void InlineTextBox::showBox(int printedCharacters) const { String value = text(); value.replaceWithLiteral('\\', "\\\\"); value.replaceWithLiteral('\n', "\\n"); printedCharacters += fprintf(stderr, "%s %p", boxName(), this); for (; printedCharacters < showTreeCharacterOffset; printedCharacters++) fputc(' ', stderr); const LineLayoutText obj = lineLayoutItem(); printedCharacters = fprintf(stderr, "\t%s %p", obj.name(), obj.debugPointer()); const int layoutObjectCharacterOffset = 75; for (; printedCharacters < layoutObjectCharacterOffset; printedCharacters++) fputc(' ', stderr); fprintf(stderr, "(%d,%d) \"%s\"\n", start(), start() + len(), value.utf8().data()); }
LayoutUnit InlineTextBox::positionForOffset(int offset) const { ASSERT(offset >= m_start); ASSERT(offset <= m_start + m_len); if (isLineBreak()) return logicalLeft(); LineLayoutText text = lineLayoutItem(); const ComputedStyle& styleToUse = text.styleRef(isFirstLineStyle()); const Font& font = styleToUse.font(); int from = !isLeftToRightDirection() ? offset - m_start : 0; int to = !isLeftToRightDirection() ? m_len : offset - m_start; // FIXME: Do we need to add rightBearing here? return font.selectionRectForText(constructTextRun(styleToUse, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX(); }
void InlineTextBox::characterWidths(Vector<float>& widths) const { FontCachePurgePreventer fontCachePurgePreventer; const ComputedStyle& styleToUse = lineLayoutItem().styleRef(isFirstLineStyle()); const Font& font = styleToUse.font(); TextRun textRun = constructTextRun(styleToUse, font); SimpleShaper shaper(&font, textRun); float lastWidth = 0; widths.resize(m_len); for (unsigned i = 0; i < m_len; i++) { shaper.advance(i + 1); widths[i] = shaper.runWidthSoFar() - lastWidth; lastWidth = shaper.runWidthSoFar(); } }
bool InlineTextBox::hasWrappedSelectionNewline() const { // TODO(wkorman): We shouldn't need layout at this point and it should // be enforced by DocumentLifecycle. http://crbug.com/537821 // Bail out as currently looking up selection state can cause the editing // code can force a re-layout while scrutinizing the editing position, and // InlineTextBox instances are not guaranteed to survive a re-layout. if (lineLayoutItem().needsLayout()) return false; SelectionState state = selectionState(); return RuntimeEnabledFeatures::selectionPaintingWithoutSelectionGapsEnabled() && (state == SelectionStart || state == SelectionInside) // Checking last leaf child can be slow, so we make sure to do this only // after the other simple conditionals. && (root().lastLeafChild() == this) // It's possible to have mixed LTR/RTL on a single line, and we only // want to paint a newline when we're the last leaf child and we make // sure there isn't a differently-directioned box following us. && ((!isLeftToRightDirection() && root().firstSelectedBox() == this) || (isLeftToRightDirection() && root().lastSelectedBox() == this)); }
bool InlineTextBox::getEmphasisMarkPosition(const ComputedStyle& style, TextEmphasisPosition& emphasisPosition) const { // This function returns true if there are text emphasis marks and they are suppressed by ruby text. if (style.textEmphasisMark() == TextEmphasisMarkNone) return false; emphasisPosition = style.textEmphasisPosition(); if (emphasisPosition == TextEmphasisPositionUnder) return true; // Ruby text is always over, so it cannot suppress emphasis marks under. LineLayoutBox containingBlock = lineLayoutItem().containingBlock(); if (!containingBlock.isRubyBase()) return true; // This text is not inside a ruby base, so it does not have ruby text over it. if (!containingBlock.parent().isRubyRun()) return true; // Cannot get the ruby text. LayoutRubyText* rubyText = LineLayoutRubyRun(containingBlock.parent()).rubyText(); // The emphasis marks over are suppressed only if there is a ruby text box and it not empty. return !rubyText || !rubyText->firstLineBox(); }
void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) const { int startPos, endPos; if (lineLayoutItem().selectionState() == SelectionInside) { startPos = 0; endPos = lineLayoutItem().textLength(); } else { lineLayoutItem().selectionStartEnd(startPos, endPos); if (lineLayoutItem().selectionState() == SelectionStart) endPos = lineLayoutItem().textLength(); else if (lineLayoutItem().selectionState() == SelectionEnd) startPos = 0; } sPos = std::max(startPos - m_start, 0); ePos = std::min(endPos - m_start, (int)m_len); }
TextRun InlineTextBox::constructTextRun(const ComputedStyle& style, const Font& font, StringView string, int maximumLength, StringBuilder* charactersWithHyphen) const { if (charactersWithHyphen) { const AtomicString& hyphenString = style.hyphenString(); charactersWithHyphen->reserveCapacity(string.length() + hyphenString.length()); charactersWithHyphen->append(string); charactersWithHyphen->append(hyphenString); string = charactersWithHyphen->toString().createView(); maximumLength = string.length(); } ASSERT(maximumLength >= static_cast<int>(string.length())); TextRun run(string, textPos().toFloat(), expansion(), expansionBehavior(), direction(), dirOverride() || style.rtlOrdering() == VisualOrder); run.setTabSize(!style.collapseWhiteSpace(), style.tabSize()); run.setCodePath(lineLayoutItem().canUseSimpleFontCodePath() ? TextRun::ForceSimple : TextRun::ForceComplex); run.setTextJustify(style.textJustify()); // Propagate the maximum length of the characters buffer to the TextRun, even when we're only processing a substring. run.setCharactersLength(maximumLength); ASSERT(run.charactersLength() >= run.length()); return run; }
IntRect EllipsisBox::selectionRect() const { const ComputedStyle& style = lineLayoutItem().styleRef(isFirstLineStyle()); const Font& font = style.font(); return enclosingIntRect(font.selectionRectForText(constructTextRun(font, m_str, style, TextRun::AllowTrailingExpansion), IntPoint(logicalLeft(), logicalTop() + root().selectionTopAdjustedForPrecedingBlock()), root().selectionHeightAdjustedForPrecedingBlock())); }
LayoutPoint InlineBox::flipForWritingMode(const LayoutPoint& point) const { if (!UNLIKELY(lineLayoutItem().hasFlippedBlocksWritingMode())) return point; return root().block().flipForWritingMode(point); }
void InlineBox::flipForWritingMode(LayoutRect& rect) const { if (!UNLIKELY(lineLayoutItem().hasFlippedBlocksWritingMode())) return; root().block().flipForWritingMode(rect); }
void InlineBox::attachLine() { m_bitfields.setExtracted(false); if (lineLayoutItem().isBox()) toLayoutBox(layoutObject()).setInlineBoxWrapper(this); }