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);
}
예제 #2
0
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);
}
예제 #3
0
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);
    }
}
예제 #4
0
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));
}