RenderObject::SelectionState InlineTextBox::selectionState() { RenderObject::SelectionState state = object()->selectionState(); if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || state == RenderObject::SelectionBoth) { int startPos, endPos; object()->selectionStartEnd(startPos, endPos); // The position after a hard line break is considered to be past its end. int lastSelectable = start() + len() - (isLineBreak() ? 1 : 0); bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= lastSelectable); if (start && end) state = RenderObject::SelectionBoth; else if (start) state = RenderObject::SelectionStart; else if (end) state = RenderObject::SelectionEnd; else if ((state == RenderObject::SelectionEnd || startPos < m_start) && (state == RenderObject::SelectionStart || endPos > lastSelectable)) state = RenderObject::SelectionInside; else if (state == RenderObject::SelectionBoth) state = RenderObject::SelectionNone; } return state; }
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; }
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(), layoutObject().style()->pointerEvents()); bool isVisible = layoutObject().style()->visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if (hitRules.canHitBoundingBox || (hitRules.canHitStroke && (layoutObject().style()->svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (layoutObject().style()->svgStyle().hasFill() || !hitRules.requireFill))) { FloatPointWillBeLayoutPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); FloatRectWillBeLayoutRect 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 (locationInContainer.intersects(rect.rawValue())) { layoutObject().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToListBasedTestResult(layoutObject().node(), locationInContainer, rect.rawValue())) return true; } } } return false; }
bool InlineTextBox::isSelected(int startPos, int endPos) const { int sPos = std::max(startPos - m_start, 0); // The position after a hard line break is considered to be past its end. // See the corresponding code in InlineTextBox::getSelectionState. int ePos = std::min(endPos - m_start, int(m_len) + (isLineBreak() ? 0 : 1)); return (sPos < ePos); }
int InlineTextBox::offsetForPosition(int _x, bool includePartialGlyphs) const { if (isLineBreak()) return 0; RenderText* text = static_cast<RenderText*>(m_object); RenderStyle *style = text->style(m_firstLine); const Font* f = &style->font(); return f->offsetForPosition(TextRun(textObject()->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || style->visuallyOrdered()), _x - m_x, includePartialGlyphs); }
bool InlineTextBox::nodeAtPoint(const HitTestRequest&, HitTestResult& result, int x, int y, int tx, int ty) { if (isLineBreak()) return false; IntRect rect(tx + m_x, ty + m_y, m_width, m_height); if (m_truncation != cFullTruncation && visibleToHitTesting() && rect.contains(x, y)) { object()->updateHitTestResult(result, IntPoint(x - tx, y - ty)); return true; } return false; }
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; }
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; }
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); }
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; }
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(); }
int InlineTextBox::positionForOffset(int offset) const { ASSERT(offset >= m_start); ASSERT(offset <= m_start + m_len); if (isLineBreak()) return m_x; RenderText* text = static_cast<RenderText*>(m_object); const Font& f = text->style(m_firstLine)->font(); int from = direction() == RTL ? offset - m_start : 0; int to = direction() == RTL ? m_len : offset - m_start; // FIXME: Do we need to add rightBearing here? return enclosingIntRect(f.selectionRectForText(TextRun(text->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride), IntPoint(m_x, 0), 0, from, to)).right(); }
bool RenderTextLineBoxes::containsOffset(const RenderText& renderer, unsigned offset, OffsetType type) const { for (auto box = m_first; box; box = box->nextTextBox()) { if (offset < box->start() && !renderer.containsReversedText()) return false; unsigned boxEnd = box->start() + box->len(); if (offset >= box->start() && offset <= boxEnd) { if (offset == boxEnd && (type == CharacterOffset || box->isLineBreak())) continue; if (type == CharacterOffset) return true; // Return false for offsets inside composed characters. return !offset || offset == static_cast<unsigned>(renderer.nextOffset(renderer.previousOffset(offset))); } } return false; }
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; }
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); }
bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, renderer()->style()->pointerEvents()); bool isVisible = renderer()->style()->visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if ((hitRules.canHitStroke && (renderer()->style()->svgStyle()->hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (renderer()->style()->svgStyle()->hasFill() || !hitRules.requireFill))) { FloatPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); FloatRect rect(boxOrigin, size()); if (rect.intersects(result.rectForPoint(pointInContainer))) { renderer()->updateHitTestResult(result, pointInContainer - toLayoutSize(accumulatedOffset)); if (!result.addNodeToRectBasedTestResult(renderer()->node(), pointInContainer, rect)) return true; } } } return false; }
bool SVGInlineTextBox::nodeAtPoint(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, LayoutUnit, LayoutUnit, HitTestAction) { // FIXME: integrate with InlineTextBox::nodeAtPoint better. ASSERT(!isLineBreak()); PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, request, renderer().style().pointerEvents()); bool isVisible = renderer().style().visibility() == VISIBLE; if (isVisible || !hitRules.requireVisible) { if ((hitRules.canHitStroke && (renderer().style().svgStyle().hasStroke() || !hitRules.requireStroke)) || (hitRules.canHitFill && (renderer().style().svgStyle().hasFill() || !hitRules.requireFill))) { FloatPoint boxOrigin(x(), y()); boxOrigin.moveBy(accumulatedOffset); FloatRect rect(boxOrigin, size()); if (locationInContainer.intersects(rect)) { float scalingFactor = renderer().scalingFactor(); ASSERT(scalingFactor); float baseline = renderer().scaledFont().fontMetrics().floatAscent() / scalingFactor; AffineTransform fragmentTransform; for (auto& fragment : m_textFragments) { FloatQuad fragmentQuad(FloatRect(fragment.x, fragment.y - baseline, fragment.width, fragment.height)); fragment.buildFragmentTransform(fragmentTransform); if (!fragmentTransform.isIdentity()) fragmentQuad = fragmentTransform.mapQuad(fragmentQuad); if (fragmentQuad.containsPoint(locationInContainer.point())) { renderer().updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset)); if (!result.addNodeToRectBasedTestResult(&renderer().textNode(), request, locationInContainer, rect)) return true; } } } } } return false; }
bool InlineTextBox::containsCaretOffset(int offset) const { // Offsets before the box are never "in". if (offset < m_start) return false; int pastEnd = m_start + m_len; // Offsets inside the box (not at either edge) are always "in". if (offset < pastEnd) return true; // Offsets outside the box are always "out". if (offset > pastEnd) return false; // Offsets at the end are "out" for line breaks (they are on the next line). if (isLineBreak()) return false; // Offsets at the end are "in" for normal boxes (but the caller has to check affinity). return true; }
static bool createLineRuns(LineState& line, const LineState& previousLine, Layout::RunVector& runs, TextFragmentIterator& textFragmentIterator) { const auto& style = textFragmentIterator.style(); line.setCollapedWhitespaceWidth(style.spaceWidth + style.wordSpacing); bool lineCanBeWrapped = style.wrapLines || style.breakWordOnOverflow; auto fragment = firstFragment(textFragmentIterator, line, previousLine, runs); while (fragment.type() != TextFragmentIterator::TextFragment::ContentEnd) { // Hard linebreak. if (fragment.isLineBreak()) { // Add the new line fragment only if there's nothing on the line. (otherwise the extra new line character would show up at the end of the content.) if (line.isEmpty() || fragment.type() == TextFragmentIterator::TextFragment::HardLineBreak) { if (style.textAlign == RIGHT || style.textAlign == WEBKIT_RIGHT) line.removeTrailingWhitespace(runs); line.appendFragmentAndCreateRunIfNeeded(fragment, runs); } break; } if (lineCanBeWrapped && !line.fits(fragment.width())) { // Overflow wrapping behaviour: // 1. Whitesapce collapse on: whitespace is skipped. Jump to next line. // 2. Whitespace collapse off: whitespace is wrapped. // 3. First, non-whitespace fragment is either wrapped or kept on the line. (depends on overflow-wrap) // 4. Non-whitespace fragment when there's already another fragment on the line gets pushed to the next line. bool emptyLine = line.isEmpty(); // Whitespace fragment. if (fragment.type() == TextFragmentIterator::TextFragment::Whitespace) { if (!style.collapseWhitespace) { // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line. line.setOverflowedFragment(splitFragmentToFitLine(fragment, line.availableWidth() - line.width(), emptyLine, textFragmentIterator)); line.appendFragmentAndCreateRunIfNeeded(fragment, runs); } // When whitespace collapse is on, whitespace that doesn't fit is simply skipped. break; } // Non-whitespace fragment. (!style.wrapLines: bug138102(preserve existing behavior) if ((emptyLine && style.breakWordOnOverflow) || !style.wrapLines) { // Split the fragment; (modified)fragment stays on this line, overflowedFragment is pushed to next line. line.setOverflowedFragment(splitFragmentToFitLine(fragment, line.availableWidth() - line.width(), emptyLine, textFragmentIterator)); line.appendFragmentAndCreateRunIfNeeded(fragment, runs); break; } // Non-breakable non-whitespace first fragment. Add it to the current line. -it overflows though. ASSERT(fragment.type() == TextFragmentIterator::TextFragment::NonWhitespace); if (emptyLine) { forceFragmentToLine(line, textFragmentIterator, runs, fragment); break; } // Non-breakable non-whitespace fragment when there's already content on the line. Push it to the next line. if (line.lastFragment().overlapsToNextRenderer()) { // Check if this fragment is a continuation of a previous segment. In such cases, we need to remove them all. const auto& lastCompleteFragment = line.revertToLastCompleteFragment(runs); textFragmentIterator.revertToEndOfFragment(lastCompleteFragment); break; } line.setOverflowedFragment(fragment); break; } line.appendFragmentAndCreateRunIfNeeded(fragment, runs); // Find the next text fragment. fragment = textFragmentIterator.nextTextFragment(line.width()); } return (fragment.type() == TextFragmentIterator::TextFragment::ContentEnd && line.overflowedFragment().isEmpty()) || line.overflowedFragment().type() == TextFragmentIterator::TextFragment::ContentEnd; }
void parseWords(QFixed minResizeWidth, int32 blockFrom) { LineBreakHelper lbh; int item = -1; int newItem = eng->findItem(0); style::align alignment = eng->option.alignment(); const QCharAttributes *attributes = eng->attributes(); if (!attributes) return; int end = 0; lbh.logClusters = eng->layoutData->logClustersPtr; block->_lpadding = 0; block->_words.clear(); int wordStart = lbh.currentPosition; bool addingEachGrapheme = false; int lastGraphemeBoundaryPosition = -1; ScriptLine lastGraphemeBoundaryLine; while (newItem < eng->layoutData->items.size()) { if (newItem != item) { item = newItem; const QScriptItem ¤t = eng->layoutData->items[item]; if (!current.num_glyphs) { eng->shape(item); attributes = eng->attributes(); if (!attributes) return; lbh.logClusters = eng->layoutData->logClustersPtr; } lbh.currentPosition = current.position; end = current.position + eng->length(item); lbh.glyphs = eng->shapedGlyphs(¤t); QFontEngine *fontEngine = eng->fontEngine(current); if (lbh.fontEngine != fontEngine) { lbh.fontEngine = fontEngine; } } const QScriptItem ¤t = eng->layoutData->items[item]; if (attributes[lbh.currentPosition].whiteSpace) { while (lbh.currentPosition < end && attributes[lbh.currentPosition].whiteSpace) addNextCluster(lbh.currentPosition, end, lbh.spaceData, lbh.glyphCount, current, lbh.logClusters, lbh.glyphs); if (block->_words.isEmpty()) { block->_lpadding = lbh.spaceData.textWidth; } else { block->_words.back().add_rpadding(lbh.spaceData.textWidth); block->_width += lbh.spaceData.textWidth; } lbh.spaceData.length = 0; lbh.spaceData.textWidth = 0; wordStart = lbh.currentPosition; addingEachGrapheme = false; lastGraphemeBoundaryPosition = -1; lastGraphemeBoundaryLine = ScriptLine(); } else { do { addNextCluster(lbh.currentPosition, end, lbh.tmpData, lbh.glyphCount, current, lbh.logClusters, lbh.glyphs); if (lbh.currentPosition >= eng->layoutData->string.length() || attributes[lbh.currentPosition].whiteSpace || isLineBreak(attributes, lbh.currentPosition)) { lbh.calculateRightBearing(); block->_words.push_back(TextWord(wordStart + blockFrom, lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block->_width += lbh.tmpData.textWidth; lbh.tmpData.textWidth = 0; lbh.tmpData.length = 0; wordStart = lbh.currentPosition; break; } else if (attributes[lbh.currentPosition].graphemeBoundary) { if (!addingEachGrapheme && lbh.tmpData.textWidth > minResizeWidth) { if (lastGraphemeBoundaryPosition >= 0) { lbh.calculateRightBearingForPreviousGlyph(); block->_words.push_back(TextWord(wordStart + blockFrom, -lastGraphemeBoundaryLine.textWidth, -lbh.negativeRightBearing())); block->_width += lastGraphemeBoundaryLine.textWidth; lbh.tmpData.textWidth -= lastGraphemeBoundaryLine.textWidth; lbh.tmpData.length -= lastGraphemeBoundaryLine.length; wordStart = lastGraphemeBoundaryPosition; } addingEachGrapheme = true; } if (addingEachGrapheme) { lbh.calculateRightBearing(); block->_words.push_back(TextWord(wordStart + blockFrom, -lbh.tmpData.textWidth, -lbh.negativeRightBearing())); block->_width += lbh.tmpData.textWidth; lbh.tmpData.textWidth = 0; lbh.tmpData.length = 0; wordStart = lbh.currentPosition; } else { lastGraphemeBoundaryPosition = lbh.currentPosition; lastGraphemeBoundaryLine = lbh.tmpData; lbh.saveCurrentGlyph(); } } } while (lbh.currentPosition < end); } if (lbh.currentPosition == end) newItem = item + 1; } if (block->_words.isEmpty()) { block->_rpadding = 0; } else { block->_rpadding = block->_words.back().f_rpadding(); block->_width -= block->_rpadding; block->_words.squeeze(); } }
void InlineTextBox::paint(RenderObject::PaintInfo& paintInfo, int tx, int ty) { if (isLineBreak() || !object()->shouldPaintWithinRoot(paintInfo) || object()->style()->visibility() != VISIBLE || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline) return; ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines); int xPos = tx + m_x - parent()->maxHorizontalVisualOverflow(); int w = width() + 2 * parent()->maxHorizontalVisualOverflow(); if (xPos >= paintInfo.rect.right() || xPos + w <= paintInfo.rect.x()) return; bool isPrinting = textObject()->document()->printing(); // Determine whether or not we're selected. bool haveSelection = !isPrinting && paintInfo.phase != PaintPhaseTextClip && selectionState() != RenderObject::SelectionNone; if (!haveSelection && paintInfo.phase == PaintPhaseSelection) // When only painting the selection, don't bother to paint if there is none. return; GraphicsContext* context = paintInfo.context; // Determine whether or not we have composition underlines to draw. bool containsComposition = object()->document()->frame()->editor()->compositionNode() == object()->node(); bool useCustomUnderlines = containsComposition && object()->document()->frame()->editor()->compositionUsesCustomUnderlines(); // Set our font. RenderStyle* styleToUse = object()->style(m_firstLine); int d = styleToUse->textDecorationsInEffect(); const Font* font = &styleToUse->font(); if (*font != context->font()) context->setFont(*font); // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection // and composition underlines. if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) { #if PLATFORM(MAC) // Custom highlighters go behind everything else. if (styleToUse->highlight() != nullAtom && !context->paintingDisabled()) paintCustomHighlight(tx, ty, styleToUse->highlight()); #endif if (containsComposition && !useCustomUnderlines) paintCompositionBackground(context, tx, ty, styleToUse, font, object()->document()->frame()->editor()->compositionStart(), object()->document()->frame()->editor()->compositionEnd()); paintDocumentMarkers(context, tx, ty, styleToUse, font, true); if (haveSelection && !useCustomUnderlines) paintSelection(context, tx, ty, styleToUse, font); } // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only). if (m_len <= 0) return; Color textFillColor; Color textStrokeColor; float textStrokeWidth = styleToUse->textStrokeWidth(); ShadowData* textShadow = paintInfo.forceBlackText ? 0 : styleToUse->textShadow(); if (paintInfo.forceBlackText) { textFillColor = Color::black; textStrokeColor = Color::black; } else { textFillColor = styleToUse->textFillColor(); if (!textFillColor.isValid()) textFillColor = styleToUse->color(); // Make the text fill color legible against a white background if (styleToUse->forceBackgroundsToWhite()) textFillColor = correctedTextColor(textFillColor, Color::white); textStrokeColor = styleToUse->textStrokeColor(); if (!textStrokeColor.isValid()) textStrokeColor = styleToUse->color(); // Make the text stroke color legible against a white background if (styleToUse->forceBackgroundsToWhite()) textStrokeColor = correctedTextColor(textStrokeColor, Color::white); } bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection); bool paintSelectedTextSeparately = false; Color selectionFillColor = textFillColor; Color selectionStrokeColor = textStrokeColor; float selectionStrokeWidth = textStrokeWidth; ShadowData* selectionShadow = textShadow; if (haveSelection) { // Check foreground color first. Color foreground = paintInfo.forceBlackText ? Color::black : object()->selectionForegroundColor(); if (foreground.isValid() && foreground != selectionFillColor) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionFillColor = foreground; } if (RenderStyle* pseudoStyle = object()->getCachedPseudoStyle(RenderStyle::SELECTION)) { ShadowData* shadow = paintInfo.forceBlackText ? 0 : pseudoStyle->textShadow(); if (shadow != selectionShadow) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionShadow = shadow; } float strokeWidth = pseudoStyle->textStrokeWidth(); if (strokeWidth != selectionStrokeWidth) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionStrokeWidth = strokeWidth; } Color stroke = paintInfo.forceBlackText ? Color::black : pseudoStyle->textStrokeColor(); if (!stroke.isValid()) stroke = pseudoStyle->color(); if (stroke != selectionStrokeColor) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionStrokeColor = stroke; } } } IntPoint textOrigin(m_x + tx, m_y + ty + m_baseline); TextRun textRun(textObject()->text()->characters() + m_start, m_len, textObject()->allowTabs(), textPos(), m_toAdd, direction() == RTL, m_dirOverride || styleToUse->visuallyOrdered()); int sPos = 0; int ePos = 0; if (paintSelectedTextOnly || paintSelectedTextSeparately) selectionStartEnd(sPos, ePos); if (!paintSelectedTextOnly) { // For stroked painting, we have to change the text drawing mode. It's probably dangerous to leave that mutated as a side // effect, so only when we know we're stroking, do a save/restore. if (textStrokeWidth > 0) context->save(); updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth); if (!paintSelectedTextSeparately || ePos <= sPos) { // FIXME: Truncate right-to-left text correctly. paintTextWithShadows(context, textRun, 0, m_truncation == cNoTruncation ? m_len : m_truncation, textOrigin, m_x + tx, m_y + ty, width(), height(), textShadow, textStrokeWidth > 0); } else paintTextWithShadows(context, textRun, ePos, sPos, textOrigin, m_x + tx, m_y + ty, width(), height(), textShadow, textStrokeWidth > 0); if (textStrokeWidth > 0) context->restore(); } if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) { // paint only the text that is selected if (selectionStrokeWidth > 0) context->save(); updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth); paintTextWithShadows(context, textRun, sPos, ePos, textOrigin, m_x + tx, m_y + ty, width(), height(), selectionShadow, selectionStrokeWidth > 0); if (selectionStrokeWidth > 0) context->restore(); } // Paint decorations if (d != TDNONE && paintInfo.phase != PaintPhaseSelection && styleToUse->htmlHacks()) { context->setStrokeColor(styleToUse->color()); paintDecoration(context, tx, ty, d, textShadow); } if (paintInfo.phase == PaintPhaseForeground) { paintDocumentMarkers(context, tx, ty, styleToUse, font, false); if (useCustomUnderlines) { const Vector<CompositionUnderline>& underlines = object()->document()->frame()->editor()->customCompositionUnderlines(); size_t numUnderlines = underlines.size(); for (size_t index = 0; index < numUnderlines; ++index) { const CompositionUnderline& underline = underlines[index]; if (underline.endOffset <= start()) // underline is completely before this run. This might be an underline that sits // before the first run we draw, or underlines that were within runs we skipped // due to truncation. continue; if (underline.startOffset <= end()) { // underline intersects this run. Paint it. paintCompositionUnderline(context, tx, ty, underline); if (underline.endOffset > end() + 1) // underline also runs into the next run. Bail now, no more marker advancement. break; } else // underline is completely after this run, bail. A later run will paint it. break; } } } }