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::paintSelection(GraphicsContext* context, const FloatPoint& boxOrigin, RenderStyle* style, const Font& font, Color textColor) { if (context->paintingDisabled()) return; // See if we have a selection to paint at all. int sPos, ePos; selectionStartEnd(sPos, ePos); if (sPos >= ePos) return; Color c = renderer().selectionBackgroundColor(); if (!c.alpha()) return; // If the text color ends up being the same as the selection background, invert the selection // background. if (textColor == c) c = Color(0xff - c.red(), 0xff - c.green(), 0xff - c.blue()); GraphicsContextStateSaver stateSaver(*context); updateGraphicsContext(context, c, c, 0); // Don't draw text at all! // If the text is truncated, let the thing being painted in the truncation // draw its own highlight. int length = m_truncation != cNoTruncation ? m_truncation : m_len; StringView string = textRenderer().text().createView(); if (string.length() != static_cast<unsigned>(length) || m_start) string.narrow(m_start, length); StringBuilder charactersWithHyphen; bool respectHyphen = ePos == length && hasHyphen(); TextRun textRun = constructTextRun(style, font, string, textRenderer().textLength() - m_start, respectHyphen ? &charactersWithHyphen : 0); if (respectHyphen) ePos = textRun.length(); LayoutUnit selectionBottom = root().selectionBottom(); LayoutUnit selectionTop = root().selectionTopAdjustedForPrecedingBlock(); int deltaY = roundToInt(renderer().style()->isFlippedLinesWritingMode() ? selectionBottom - logicalBottom() : logicalTop() - selectionTop); int selHeight = max(0, roundToInt(selectionBottom - selectionTop)); FloatPoint localOrigin(boxOrigin.x(), boxOrigin.y() - deltaY); FloatRect clipRect(localOrigin, FloatSize(m_logicalWidth, selHeight)); alignSelectionRectToDevicePixels(clipRect); context->clip(clipRect); context->drawHighlightForText(font, textRun, localOrigin, selHeight, c, sPos, ePos); }
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); } }
void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, LayoutUnit /*lineTop*/, LayoutUnit /*lineBottom*/) { if (isLineBreak() || !paintInfo.shouldPaintWithinRoot(&renderer()) || renderer().style()->visibility() != VISIBLE || m_truncation == cFullTruncation || paintInfo.phase == PaintPhaseOutline || !m_len) return; ASSERT(paintInfo.phase != PaintPhaseSelfOutline && paintInfo.phase != PaintPhaseChildOutlines); LayoutUnit logicalLeftSide = logicalLeftVisualOverflow(); LayoutUnit logicalRightSide = logicalRightVisualOverflow(); LayoutUnit logicalStart = logicalLeftSide + (isHorizontal() ? paintOffset.x() : paintOffset.y()); LayoutUnit logicalExtent = logicalRightSide - logicalLeftSide; LayoutUnit paintEnd = isHorizontal() ? paintInfo.rect.maxX() : paintInfo.rect.maxY(); LayoutUnit paintStart = isHorizontal() ? paintInfo.rect.x() : paintInfo.rect.y(); // When subpixel font scaling is enabled text runs are positioned at // subpixel boundaries on the x-axis and thus there is no reason to // snap the x value. We still round the y-axis to ensure consistent // line heights. LayoutPoint adjustedPaintOffset = RuntimeEnabledFeatures::subpixelFontScalingEnabled() ? LayoutPoint(paintOffset.x(), paintOffset.y().round()) : roundedIntPoint(paintOffset); if (logicalStart >= paintEnd || logicalStart + logicalExtent <= paintStart) return; bool isPrinting = textRenderer().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; if (m_truncation != cNoTruncation) { if (renderer().containingBlock()->style()->isLeftToRightDirection() != isLeftToRightDirection()) { // Make the visible fragment of text hug the edge closest to the rest of the run by moving the origin // at which we start drawing text. // e.g. In the case of LTR text truncated in an RTL Context, the correct behavior is: // |Hello|CBA| -> |...He|CBA| // In order to draw the fragment "He" aligned to the right edge of it's box, we need to start drawing // farther to the right. // NOTE: WebKit's behavior differs from that of IE which appears to just overlay the ellipsis on top of the // truncated string i.e. |Hello|CBA| -> |...lo|CBA| LayoutUnit widthOfVisibleText = toRenderText(renderer()).width(m_start, m_truncation, textPos(), isLeftToRightDirection() ? LTR : RTL, isFirstLineStyle()); LayoutUnit widthOfHiddenText = m_logicalWidth - widthOfVisibleText; // FIXME: The hit testing logic also needs to take this translation into account. LayoutSize truncationOffset(isLeftToRightDirection() ? widthOfHiddenText : -widthOfHiddenText, 0); adjustedPaintOffset.move(isHorizontal() ? truncationOffset : truncationOffset.transposedSize()); } } GraphicsContext* context = paintInfo.context; RenderObject& rendererToUse = renderer(); RenderStyle* styleToUse = rendererToUse.style(isFirstLineStyle()); adjustedPaintOffset.move(0, styleToUse->isHorizontalWritingMode() ? 0 : -logicalHeight()); FloatPoint boxOrigin = locationIncludingFlipping(); boxOrigin.move(adjustedPaintOffset.x().toFloat(), adjustedPaintOffset.y().toFloat()); FloatRect boxRect(boxOrigin, LayoutSize(logicalWidth(), logicalHeight())); RenderCombineText* combinedText = styleToUse->hasTextCombine() && textRenderer().isCombineText() && toRenderCombineText(textRenderer()).isCombined() ? &toRenderCombineText(textRenderer()) : 0; bool shouldRotate = !isHorizontal() && !combinedText; if (shouldRotate) context->concatCTM(rotation(boxRect, Clockwise)); // Determine whether or not we have composition underlines to draw. bool containsComposition = renderer().node() && renderer().frame()->inputMethodController().compositionNode() == renderer().node(); bool useCustomUnderlines = containsComposition && renderer().frame()->inputMethodController().compositionUsesCustomUnderlines(); // Determine the text colors and selection colors. Color textFillColor; Color textStrokeColor; Color emphasisMarkColor; float textStrokeWidth = styleToUse->textStrokeWidth(); // Text shadows are disabled when printing. http://crbug.com/258321 const ShadowList* textShadow = (context->printing() || paintInfo.forceBlackText()) ? 0 : styleToUse->textShadow(); if (paintInfo.forceBlackText()) { textFillColor = Color::black; textStrokeColor = Color::black; emphasisMarkColor = Color::black; } else { textFillColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextFillColor); bool forceBackgroundToWhite = false; if (isPrinting) { if (styleToUse->printColorAdjust() == PrintColorAdjustEconomy) forceBackgroundToWhite = true; if (textRenderer().document().settings() && textRenderer().document().settings()->shouldPrintBackgrounds()) forceBackgroundToWhite = false; } // Make the text fill color legible against a white background if (forceBackgroundToWhite) textFillColor = correctedTextColor(textFillColor, Color::white); textStrokeColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextStrokeColor); // Make the text stroke color legible against a white background if (forceBackgroundToWhite) textStrokeColor = correctedTextColor(textStrokeColor, Color::white); emphasisMarkColor = rendererToUse.resolveColor(styleToUse, CSSPropertyWebkitTextEmphasisColor); // Make the text stroke color legible against a white background if (forceBackgroundToWhite) emphasisMarkColor = correctedTextColor(emphasisMarkColor, Color::white); } bool paintSelectedTextOnly = (paintInfo.phase == PaintPhaseSelection); bool paintSelectedTextSeparately = false; Color selectionFillColor = textFillColor; Color selectionStrokeColor = textStrokeColor; Color selectionEmphasisMarkColor = emphasisMarkColor; float selectionStrokeWidth = textStrokeWidth; const ShadowList* selectionShadow = textShadow; if (haveSelection) { // Check foreground color first. Color foreground = paintInfo.forceBlackText() ? Color::black : renderer().selectionForegroundColor(); if (foreground != selectionFillColor) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionFillColor = foreground; } Color emphasisMarkForeground = paintInfo.forceBlackText() ? Color::black : renderer().selectionEmphasisMarkColor(); if (emphasisMarkForeground != selectionEmphasisMarkColor) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionEmphasisMarkColor = emphasisMarkForeground; } if (RenderStyle* pseudoStyle = renderer().getCachedPseudoStyle(SELECTION)) { // Text shadows are disabled when printing. http://crbug.com/258321 const ShadowList* shadow = (context->printing() || 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 : rendererToUse.resolveColor(pseudoStyle, CSSPropertyWebkitTextStrokeColor); if (stroke != selectionStrokeColor) { if (!paintSelectedTextOnly) paintSelectedTextSeparately = true; selectionStrokeColor = stroke; } } } // Set our font. const Font& font = styleToUse->font(); FloatPoint textOrigin = FloatPoint(boxOrigin.x(), boxOrigin.y() + font.fontMetrics().ascent()); if (combinedText) combinedText->adjustTextOrigin(textOrigin, boxRect); // 1. Paint backgrounds behind text if needed. Examples of such backgrounds include selection // and composition highlights. if (paintInfo.phase != PaintPhaseSelection && paintInfo.phase != PaintPhaseTextClip && !isPrinting) { if (containsComposition) { paintCompositionBackgrounds(context, boxOrigin, styleToUse, font, useCustomUnderlines); } paintDocumentMarkers(context, boxOrigin, styleToUse, font, true); if (haveSelection && !useCustomUnderlines) paintSelection(context, boxOrigin, styleToUse, font, selectionFillColor); } // 2. Now paint the foreground, including text and decorations like underline/overline (in quirks mode only). int length = m_len; int maximumLength; StringView string; if (!combinedText) { string = textRenderer().text().createView(); if (static_cast<unsigned>(length) != string.length() || m_start) string.narrow(m_start, length); maximumLength = textRenderer().textLength() - m_start; } else { combinedText->getStringToRender(m_start, string, length); maximumLength = length; } StringBuilder charactersWithHyphen; TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0); if (hasHyphen()) length = textRun.length(); int sPos = 0; int ePos = 0; if (paintSelectedTextOnly || paintSelectedTextSeparately) selectionStartEnd(sPos, ePos); if (m_truncation != cNoTruncation) { sPos = min<int>(sPos, m_truncation); ePos = min<int>(ePos, m_truncation); length = m_truncation; } int emphasisMarkOffset = 0; TextEmphasisPosition emphasisMarkPosition; bool hasTextEmphasis = getEmphasisMarkPosition(styleToUse, emphasisMarkPosition); const AtomicString& emphasisMark = hasTextEmphasis ? styleToUse->textEmphasisMarkString() : nullAtom; if (!emphasisMark.isEmpty()) emphasisMarkOffset = emphasisMarkPosition == TextEmphasisPositionOver ? -font.fontMetrics().ascent() - font.emphasisMarkDescent(emphasisMark) : font.fontMetrics().descent() + font.emphasisMarkAscent(emphasisMark); 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. GraphicsContextStateSaver stateSaver(*context, textStrokeWidth > 0); updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth); if (!paintSelectedTextSeparately || ePos <= sPos) { // FIXME: Truncate right-to-left text correctly. paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, 0, length, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); } else { paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, ePos, sPos, length, textOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); } if (!emphasisMark.isEmpty()) { updateGraphicsContext(context, emphasisMarkColor, textStrokeColor, textStrokeWidth); DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1)); TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun; FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin; if (combinedText) context->concatCTM(rotation(boxRect, Clockwise)); int startOffset = 0; int endOffset = length; int paintRunLength = length; if (combinedText) { startOffset = 0; endOffset = objectReplacementCharacterTextRun.length(); paintRunLength = endOffset; } else if (paintSelectedTextSeparately && ePos > sPos) { startOffset = ePos; endOffset = sPos; } // FIXME: Truncate right-to-left text correctly. paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, startOffset, endOffset, paintRunLength, emphasisMarkTextOrigin, boxRect, textShadow, textStrokeWidth > 0, isHorizontal()); if (combinedText) context->concatCTM(rotation(boxRect, Counterclockwise)); } } if ((paintSelectedTextOnly || paintSelectedTextSeparately) && sPos < ePos) { // paint only the text that is selected GraphicsContextStateSaver stateSaver(*context, selectionStrokeWidth > 0); updateGraphicsContext(context, selectionFillColor, selectionStrokeColor, selectionStrokeWidth); paintTextWithShadows(context, rendererToUse, font, textRun, nullAtom, 0, sPos, ePos, length, textOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal()); if (!emphasisMark.isEmpty()) { updateGraphicsContext(context, selectionEmphasisMarkColor, textStrokeColor, textStrokeWidth); DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (&objectReplacementCharacter, 1)); TextRun& emphasisMarkTextRun = combinedText ? objectReplacementCharacterTextRun : textRun; FloatPoint emphasisMarkTextOrigin = combinedText ? FloatPoint(boxOrigin.x() + boxRect.width() / 2, boxOrigin.y() + font.fontMetrics().ascent()) : textOrigin; if (combinedText) context->concatCTM(rotation(boxRect, Clockwise)); int startOffset = combinedText ? 0 : sPos; int endOffset = combinedText ? objectReplacementCharacterTextRun.length() : ePos; int paintRunLength = combinedText ? endOffset : length; paintTextWithShadows(context, rendererToUse, combinedText ? combinedText->originalFont() : font, emphasisMarkTextRun, emphasisMark, emphasisMarkOffset, startOffset, endOffset, paintRunLength, emphasisMarkTextOrigin, boxRect, selectionShadow, selectionStrokeWidth > 0, isHorizontal()); if (combinedText) context->concatCTM(rotation(boxRect, Counterclockwise)); } } // Paint decorations TextDecoration textDecorations = styleToUse->textDecorationsInEffect(); if (textDecorations != TextDecorationNone && paintInfo.phase != PaintPhaseSelection) { updateGraphicsContext(context, textFillColor, textStrokeColor, textStrokeWidth); if (combinedText) context->concatCTM(rotation(boxRect, Clockwise)); paintDecoration(context, boxOrigin, textDecorations, textShadow); if (combinedText) context->concatCTM(rotation(boxRect, Counterclockwise)); } if (paintInfo.phase == PaintPhaseForeground) { paintDocumentMarkers(context, boxOrigin, styleToUse, font, false); // Paint custom underlines for compositions. if (useCustomUnderlines) { const Vector<CompositionUnderline>& underlines = renderer().frame()->inputMethodController().customCompositionUnderlines(); CompositionUnderlineRangeFilter filter(underlines, start(), end()); for (CompositionUnderlineRangeFilter::ConstIterator it = filter.begin(); it != filter.end(); ++it) { if (it->color == Color::transparent) continue; paintCompositionUnderline(context, boxOrigin, *it); } } } if (shouldRotate) context->concatCTM(rotation(boxRect, Counterclockwise)); }