blob: 95c02bdc54bdc641501c9078a7330a6fae5dbfce [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/paint/TextPainter.h"
#include "core/CSSPropertyNames.h"
#include "core/frame/Settings.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/LayoutTextCombine.h"
#include "core/layout/api/LineLayoutAPIShim.h"
#include "core/layout/api/LineLayoutItem.h"
#include "core/paint/BoxPainter.h"
#include "core/paint/PaintInfo.h"
#include "core/style/ComputedStyle.h"
#include "core/style/ShadowList.h"
#include "platform/fonts/Font.h"
#include "platform/graphics/GraphicsContext.h"
#include "platform/graphics/GraphicsContextStateSaver.h"
#include "platform/text/TextRun.h"
#include "wtf/Assertions.h"
#include "wtf/text/CharacterNames.h"
namespace blink {
TextPainter::TextPainter(GraphicsContext& context,
const Font& font,
const TextRun& run,
const LayoutPoint& textOrigin,
const LayoutRect& textBounds,
bool horizontal)
: m_graphicsContext(context),
m_font(font),
m_run(run),
m_textOrigin(textOrigin),
m_textBounds(textBounds),
m_horizontal(horizontal),
m_emphasisMarkOffset(0),
m_combinedText(0) {}
TextPainter::~TextPainter() {}
void TextPainter::setEmphasisMark(const AtomicString& emphasisMark,
TextEmphasisPosition position) {
m_emphasisMark = emphasisMark;
const SimpleFontData* fontData = m_font.primaryFont();
DCHECK(fontData);
if (!fontData || emphasisMark.isNull()) {
m_emphasisMarkOffset = 0;
} else if (position == TextEmphasisPositionOver) {
m_emphasisMarkOffset = -fontData->getFontMetrics().ascent() -
m_font.emphasisMarkDescent(emphasisMark);
} else {
DCHECK(position == TextEmphasisPositionUnder);
m_emphasisMarkOffset = fontData->getFontMetrics().descent() +
m_font.emphasisMarkAscent(emphasisMark);
}
}
void TextPainter::paint(unsigned startOffset,
unsigned endOffset,
unsigned length,
const Style& textStyle,
TextBlobPtr* cachedTextBlob) {
GraphicsContextStateSaver stateSaver(m_graphicsContext, false);
updateGraphicsContext(textStyle, stateSaver);
if (m_combinedText) {
m_graphicsContext.save();
m_combinedText->transformToInlineCoordinates(m_graphicsContext,
m_textBounds);
paintInternal<PaintText>(startOffset, endOffset, length, cachedTextBlob);
m_graphicsContext.restore();
} else {
paintInternal<PaintText>(startOffset, endOffset, length, cachedTextBlob);
}
if (!m_emphasisMark.isEmpty()) {
if (textStyle.emphasisMarkColor != textStyle.fillColor)
m_graphicsContext.setFillColor(textStyle.emphasisMarkColor);
if (m_combinedText)
paintEmphasisMarkForCombinedText();
else
paintInternal<PaintEmphasisMark>(startOffset, endOffset, length);
}
}
// static
void TextPainter::updateGraphicsContext(GraphicsContext& context,
const Style& textStyle,
bool horizontal,
GraphicsContextStateSaver& stateSaver) {
TextDrawingModeFlags mode = context.textDrawingMode();
if (textStyle.strokeWidth > 0) {
TextDrawingModeFlags newMode = mode | TextModeStroke;
if (mode != newMode) {
if (!stateSaver.saved())
stateSaver.save();
context.setTextDrawingMode(newMode);
mode = newMode;
}
}
if (mode & TextModeFill && textStyle.fillColor != context.fillColor())
context.setFillColor(textStyle.fillColor);
if (mode & TextModeStroke) {
if (textStyle.strokeColor != context.strokeColor())
context.setStrokeColor(textStyle.strokeColor);
if (textStyle.strokeWidth != context.strokeThickness())
context.setStrokeThickness(textStyle.strokeWidth);
}
if (textStyle.shadow) {
if (!stateSaver.saved())
stateSaver.save();
context.setDrawLooper(textStyle.shadow->createDrawLooper(
DrawLooperBuilder::ShadowIgnoresAlpha, textStyle.currentColor,
horizontal));
}
}
Color TextPainter::textColorForWhiteBackground(Color textColor) {
int distanceFromWhite = differenceSquared(textColor, Color::white);
// semi-arbitrarily chose 65025 (255^2) value here after a few tests;
return distanceFromWhite > 65025 ? textColor : textColor.dark();
}
// static
TextPainter::Style TextPainter::textPaintingStyle(LineLayoutItem lineLayoutItem,
const ComputedStyle& style,
const PaintInfo& paintInfo) {
TextPainter::Style textStyle;
bool isPrinting = paintInfo.isPrinting();
if (paintInfo.phase == PaintPhaseTextClip) {
// When we use the text as a clip, we only care about the alpha, thus we
// make all the colors black.
textStyle.currentColor = Color::black;
textStyle.fillColor = Color::black;
textStyle.strokeColor = Color::black;
textStyle.emphasisMarkColor = Color::black;
textStyle.strokeWidth = style.textStrokeWidth();
textStyle.shadow = 0;
} else {
textStyle.currentColor = style.visitedDependentColor(CSSPropertyColor);
textStyle.fillColor =
lineLayoutItem.resolveColor(style, CSSPropertyWebkitTextFillColor);
textStyle.strokeColor =
lineLayoutItem.resolveColor(style, CSSPropertyWebkitTextStrokeColor);
textStyle.emphasisMarkColor =
lineLayoutItem.resolveColor(style, CSSPropertyWebkitTextEmphasisColor);
textStyle.strokeWidth = style.textStrokeWidth();
textStyle.shadow = style.textShadow();
// Adjust text color when printing with a white background.
DCHECK(lineLayoutItem.document().printing() == isPrinting);
bool forceBackgroundToWhite =
BoxPainter::shouldForceWhiteBackgroundForPrintEconomy(
style, lineLayoutItem.document());
if (forceBackgroundToWhite) {
textStyle.fillColor = textColorForWhiteBackground(textStyle.fillColor);
textStyle.strokeColor =
textColorForWhiteBackground(textStyle.strokeColor);
textStyle.emphasisMarkColor =
textColorForWhiteBackground(textStyle.emphasisMarkColor);
}
// Text shadows are disabled when printing. http://crbug.com/258321
if (isPrinting)
textStyle.shadow = 0;
}
return textStyle;
}
TextPainter::Style TextPainter::selectionPaintingStyle(
LineLayoutItem lineLayoutItem,
bool haveSelection,
const PaintInfo& paintInfo,
const TextPainter::Style& textStyle) {
const LayoutObject& layoutObject =
*LineLayoutAPIShim::constLayoutObjectFrom(lineLayoutItem);
TextPainter::Style selectionStyle = textStyle;
bool usesTextAsClip = paintInfo.phase == PaintPhaseTextClip;
bool isPrinting = paintInfo.isPrinting();
if (haveSelection) {
if (!usesTextAsClip) {
selectionStyle.fillColor = layoutObject.selectionForegroundColor(
paintInfo.getGlobalPaintFlags());
selectionStyle.emphasisMarkColor =
layoutObject.selectionEmphasisMarkColor(
paintInfo.getGlobalPaintFlags());
}
if (const ComputedStyle* pseudoStyle =
layoutObject.getCachedPseudoStyle(PseudoIdSelection)) {
selectionStyle.strokeColor =
usesTextAsClip ? Color::black
: layoutObject.resolveColor(
*pseudoStyle, CSSPropertyWebkitTextStrokeColor);
selectionStyle.strokeWidth = pseudoStyle->textStrokeWidth();
selectionStyle.shadow = usesTextAsClip ? 0 : pseudoStyle->textShadow();
}
// Text shadows are disabled when printing. http://crbug.com/258321
if (isPrinting)
selectionStyle.shadow = 0;
}
return selectionStyle;
}
template <TextPainter::PaintInternalStep step>
void TextPainter::paintInternalRun(TextRunPaintInfo& textRunPaintInfo,
unsigned from,
unsigned to) {
DCHECK(from <= textRunPaintInfo.run.length());
DCHECK(to <= textRunPaintInfo.run.length());
textRunPaintInfo.from = from;
textRunPaintInfo.to = to;
if (step == PaintEmphasisMark) {
m_graphicsContext.drawEmphasisMarks(
m_font, textRunPaintInfo, m_emphasisMark,
FloatPoint(m_textOrigin) + IntSize(0, m_emphasisMarkOffset));
} else {
DCHECK(step == PaintText);
m_graphicsContext.drawText(m_font, textRunPaintInfo,
FloatPoint(m_textOrigin));
}
}
template <TextPainter::PaintInternalStep Step>
void TextPainter::paintInternal(unsigned startOffset,
unsigned endOffset,
unsigned truncationPoint,
TextBlobPtr* cachedTextBlob) {
TextRunPaintInfo textRunPaintInfo(m_run);
textRunPaintInfo.bounds = FloatRect(m_textBounds);
if (startOffset <= endOffset) {
// FIXME: We should be able to use cachedTextBlob in more cases.
textRunPaintInfo.cachedTextBlob = cachedTextBlob;
paintInternalRun<Step>(textRunPaintInfo, startOffset, endOffset);
} else {
if (endOffset > 0)
paintInternalRun<Step>(textRunPaintInfo, 0, endOffset);
if (startOffset < truncationPoint)
paintInternalRun<Step>(textRunPaintInfo, startOffset, truncationPoint);
}
}
void TextPainter::clipDecorationsStripe(float upper,
float stripeWidth,
float dilation) {
TextRunPaintInfo textRunPaintInfo(m_run);
if (!m_run.length())
return;
Vector<Font::TextIntercept> textIntercepts;
m_font.getTextIntercepts(
textRunPaintInfo, m_graphicsContext.deviceScaleFactor(),
m_graphicsContext.fillPaint(),
std::make_tuple(upper, upper + stripeWidth), textIntercepts);
for (auto intercept : textIntercepts) {
FloatPoint clipOrigin(m_textOrigin);
FloatRect clipRect(
clipOrigin + FloatPoint(intercept.m_begin, upper),
FloatSize(intercept.m_end - intercept.m_begin, stripeWidth));
clipRect.inflateX(dilation);
// We need to ensure the clip rectangle is covering the full underline
// extent. For horizontal drawing, using enclosingIntRect would be
// sufficient, since we can clamp to full device pixels that way. However,
// for vertical drawing, we have a transformation applied, which breaks the
// integers-equal-device pixels assumption, so vertically inflating by 1
// pixel makes sure we're always covering. This should only be done on the
// clipping rectangle, not when computing the glyph intersects.
clipRect.inflateY(1.0);
m_graphicsContext.clipOut(clipRect);
}
}
void TextPainter::paintEmphasisMarkForCombinedText() {
const SimpleFontData* fontData = m_font.primaryFont();
DCHECK(fontData);
if (!fontData)
return;
DCHECK(m_combinedText);
TextRun placeholderTextRun(&ideographicFullStopCharacter, 1);
FloatPoint emphasisMarkTextOrigin(m_textBounds.x().toFloat(),
m_textBounds.y().toFloat() +
fontData->getFontMetrics().ascent() +
m_emphasisMarkOffset);
TextRunPaintInfo textRunPaintInfo(placeholderTextRun);
textRunPaintInfo.bounds = FloatRect(m_textBounds);
m_graphicsContext.concatCTM(rotation(m_textBounds, Clockwise));
m_graphicsContext.drawEmphasisMarks(m_combinedText->originalFont(),
textRunPaintInfo, m_emphasisMark,
emphasisMarkTextOrigin);
m_graphicsContext.concatCTM(rotation(m_textBounds, Counterclockwise));
}
} // namespace blink