int InlineTextBox::baselinePosition(FontBaseline baselineType) const { if (!isText() || !parent()) return 0; if (parent()->getLineLayoutItem() == getLineLayoutItem().parent()) return parent()->baselinePosition(baselineType); return LineLayoutBoxModel(getLineLayoutItem().parent()).baselinePosition(baselineType, isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); }
LayoutUnit InlineTextBox::lineHeight() const { if (!isText() || !getLineLayoutItem().parent()) return LayoutUnit(); if (getLineLayoutItem().isBR()) return LayoutUnit(toLayoutBR(getLineLayoutItem())->lineHeight(isFirstLineStyle())); if (parent()->getLineLayoutItem() == getLineLayoutItem().parent()) return parent()->lineHeight(); return LineLayoutBoxModel(getLineLayoutItem().parent()).lineHeight(isFirstLineStyle(), isHorizontal() ? HorizontalLine : VerticalLine, PositionOnContainingLine); }
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(), getLineLayoutItem().style()->pointerEvents()); bool isVisible = getLineLayoutItem().style()->visibility() == EVisibility::Visible; if (isVisible || !hitRules.requireVisible) { if (hitRules.canHitBoundingBox || (hitRules.canHitStroke && (getLineLayoutItem().style()->svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (getLineLayoutItem().style()->svgStyle().hasFill() || !hitRules.requireFill))) { LayoutRect rect(topLeft(), LayoutSize(logicalWidth(), logicalHeight())); rect.moveBy(accumulatedOffset); if (locationInContainer.intersects(rect)) { LineLayoutSVGInlineText lineLayoutItem = LineLayoutSVGInlineText(this->getLineLayoutItem()); const SimpleFontData* fontData = lineLayoutItem.scaledFont().primaryFont(); DCHECK(fontData); if (!fontData) return false; DCHECK(lineLayoutItem.scalingFactor()); float baseline = fontData->getFontMetrics().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) == StopHitTesting) return true; } } } } } return false; }
TextRun InlineTextBox::constructTextRun( const ComputedStyle& style, StringBuilder* charactersWithHyphen) const { ASSERT(getLineLayoutItem().text()); String string = getLineLayoutItem().text(); unsigned startPos = start(); unsigned length = len(); return constructTextRun(style, StringView(string, startPos, length), getLineLayoutItem().textLength() - startPos, charactersWithHyphen); }
SelectionState InlineTextBox::getSelectionState() const { SelectionState state = getLineLayoutItem().getSelectionState(); if (state == SelectionStart || state == SelectionEnd || state == SelectionBoth) { int startPos, endPos; getLineLayoutItem().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 = getLineLayoutItem().style()->getLineBreak() == 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; }
TextRun InlineTextBox::constructTextRun(const ComputedStyle& style, const Font& font, StringBuilder* charactersWithHyphen) const { ASSERT(getLineLayoutItem().text()); StringView string = getLineLayoutItem().text().createView(); unsigned startPos = start(); unsigned length = len(); if (string.length() != length || startPos) string.narrow(startPos, length); return constructTextRun(style, font, string, getLineLayoutItem().textLength() - startPos, charactersWithHyphen); }
bool InlineTextBox::nodeAtPoint(HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit /* lineTop */, LayoutUnit /*lineBottom*/) { if (isLineBreak() || m_truncation == cFullTruncation) return false; LayoutPoint boxOrigin = locationIncludingFlipping(); boxOrigin.moveBy(accumulatedOffset); LayoutRect rect(boxOrigin, size()); if (visibleToHitTestRequest(result.hitTestRequest()) && locationInContainer.intersects(rect)) { getLineLayoutItem().updateHitTestResult(result, flipForWritingMode(locationInContainer.point() - toLayoutSize(accumulatedOffset))); if (result.addNodeToListBasedTestResult(getLineLayoutItem().node(), locationInContainer, rect) == StopHitTesting) return true; } return false; }
void InlineTextBox::attachLine() { if (!extracted()) return; getLineLayoutItem().attachTextBox(this); }
void InlineTextBox::extractLine() { if (extracted()) return; getLineLayoutItem().extractTextBox(this); }
TextRun SVGInlineTextBox::constructTextRun( const ComputedStyle& style, const SVGTextFragment& fragment) const { LineLayoutText text = getLineLayoutItem(); CHECK(!text.needsLayout()); TextRun run( // characters, will be set below if non-zero. static_cast<const LChar*>(nullptr), 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; }
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 = getLineLayoutItem().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)); }
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.getTextEmphasisMark() == TextEmphasisMarkNone) return false; emphasisPosition = style.getTextEmphasisPosition(); // Ruby text is always over, so it cannot suppress emphasis marks under. if (emphasisPosition == TextEmphasisPositionUnder) return true; LineLayoutBox containingBlock = getLineLayoutItem().containingBlock(); // This text is not inside a ruby base, so it does not have ruby text over it. if (!containingBlock.isRubyBase()) return true; // Cannot get the ruby text. if (!containingBlock.parent().isRubyRun()) return true; LineLayoutRubyText 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(); }
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 = getLineLayoutItem().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 = LayoutUnit(); 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(LayoutUnit(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)); }
void InlineTextBox::characterWidths(Vector<float>& widths) const { if (!m_len) return; FontCachePurgePreventer fontCachePurgePreventer; ASSERT(getLineLayoutItem().text()); const ComputedStyle& styleToUse = getLineLayoutItem().styleRef(isFirstLineStyle()); const Font& font = styleToUse.font(); TextRun textRun = constructTextRun(styleToUse); Vector<CharacterRange> ranges = font.individualCharacterRanges(textRun); DCHECK_EQ(ranges.size(), m_len); widths.resize(ranges.size()); for (unsigned i = 0; i < ranges.size(); i++) widths[i] = ranges[i].width(); }
void InlineTextBox::characterWidths(Vector<float>& widths) const { if (!m_len) return; FontCachePurgePreventer fontCachePurgePreventer; ASSERT(getLineLayoutItem().text()); const ComputedStyle& styleToUse = getLineLayoutItem().styleRef(isFirstLineStyle()); const Font& font = styleToUse.font(); float lastWidth = 0; widths.resize(m_len); for (unsigned i = 0; i < m_len; i++) { StringView substringView = getLineLayoutItem().text().createView(); substringView.narrow(start(), 1 + i); TextRun textRun = constructTextRun(styleToUse, font, substringView, m_len); widths[i] = font.width(textRun, nullptr, nullptr) - lastWidth; lastWidth = font.width(textRun, nullptr, nullptr); } }
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 = getLineLayoutItem(); 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 = getLineLayoutItem(); 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 = getLineLayoutItem(); 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 LayoutUnit(font.selectionRectForText(constructTextRun(styleToUse, font), IntPoint(logicalLeft(), 0), 0, from, to).maxX()); }
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 (getLineLayoutItem().needsLayout()) return false; SelectionState state = getSelectionState(); return (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)); }
void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) const { int startPos, endPos; if (getLineLayoutItem().getSelectionState() == SelectionInside) { startPos = 0; endPos = getLineLayoutItem().textLength(); } else { getLineLayoutItem().selectionStartEnd(startPos, endPos); if (getLineLayoutItem().getSelectionState() == SelectionStart) endPos = getLineLayoutItem().textLength(); else if (getLineLayoutItem().getSelectionState() == SelectionEnd) startPos = 0; } sPos = std::max(startPos - m_start, 0); ePos = std::min(endPos - m_start, (int)m_len); }
String InlineTextBox::text() const { return getLineLayoutItem().text().substring(start(), len()); }
bool InlineTextBox::isLineBreak() const { return getLineLayoutItem().isBR() || (getLineLayoutItem().style()->preserveNewline() && len() == 1 && (*getLineLayoutItem().text().impl())[start()] == '\n'); }
LayoutUnit InlineTextBox::placeEllipsisBox(bool flowIsLTR, LayoutUnit visibleLeftEdge, LayoutUnit visibleRightEdge, LayoutUnit ellipsisWidth, LayoutUnit& truncatedWidth, bool& foundBox) { if (foundBox) { setTruncation(cFullTruncation); return LayoutUnit(-1); } // For LTR this is the left edge of the box, for RTL, the right edge in parent // coordinates. LayoutUnit ellipsisX = flowIsLTR ? visibleRightEdge - ellipsisWidth : visibleLeftEdge + ellipsisWidth; // Criteria for full truncation: // LTR: the left edge of the ellipsis is to the left of our text run. // RTL: the right edge of the ellipsis is to the right of our text run. bool ltrFullTruncation = flowIsLTR && ellipsisX <= logicalLeft(); bool rtlFullTruncation = !flowIsLTR && ellipsisX >= logicalLeft() + logicalWidth(); if (ltrFullTruncation || rtlFullTruncation) { // Too far. Just set full truncation, but return -1 and let the ellipsis // just be placed at the edge of the box. setTruncation(cFullTruncation); foundBox = true; return LayoutUnit(-1); } bool ltrEllipsisWithinBox = flowIsLTR && (ellipsisX < logicalRight()); bool rtlEllipsisWithinBox = !flowIsLTR && (ellipsisX > logicalLeft()); if (ltrEllipsisWithinBox || rtlEllipsisWithinBox) { foundBox = true; // The inline box may have different directionality than it's parent. Since // truncation behavior depends both on both the parent and the inline // block's directionality, we must keep track of these separately. bool ltr = isLeftToRightDirection(); if (ltr != flowIsLTR) { // Width in pixels of the visible portion of the box, excluding the // ellipsis. int visibleBoxWidth = (visibleRightEdge - visibleLeftEdge - ellipsisWidth).toInt(); ellipsisX = flowIsLTR ? logicalLeft() + visibleBoxWidth : logicalRight() - visibleBoxWidth; } // The box's width includes partial glyphs, so respect that when placing // the ellipsis. int offset = offsetForPosition(ellipsisX); if (offset == 0 && ltr == flowIsLTR) { // No characters should be laid out. Set ourselves to full truncation and // place the ellipsis at the min of our start and the ellipsis edge. setTruncation(cFullTruncation); truncatedWidth += ellipsisWidth; return std::min(ellipsisX, logicalLeft()); } // Set the truncation index on the text run. setTruncation(offset); // If we got here that means that we were only partially truncated and we // need to return the pixel offset at which to place the ellipsis. Where the // text and its flow have opposite directions then our offset into the text // is at the start of the part that will be visible. LayoutUnit widthOfVisibleText(getLineLayoutItem().width( ltr == flowIsLTR ? m_start : m_start + offset, ltr == flowIsLTR ? offset : m_len - offset, textPos(), flowIsLTR ? LTR : RTL, isFirstLineStyle())); // The ellipsis needs to be placed just after the last visible character. // Where "after" is defined by the flow directionality, not the inline // box directionality. // e.g. In the case of an LTR inline box truncated in an RTL flow then we // can have a situation such as |Hello| -> |...He| truncatedWidth += widthOfVisibleText + ellipsisWidth; if (flowIsLTR) return logicalLeft() + widthOfVisibleText; return logicalRight() - widthOfVisibleText - ellipsisWidth; } truncatedWidth += logicalWidth(); return LayoutUnit(-1); }
void InlineTextBox::deleteLine() { getLineLayoutItem().removeTextBox(this); destroy(); }
float InlineTextBox::newlineSpaceWidth() const { const ComputedStyle& styleToUse = getLineLayoutItem().styleRef(isFirstLineStyle()); return styleToUse.font().spaceWidth(); }