#include "config.h"
#include "TextPainter.h"
#include "GraphicsContext.h"
#include "InlineTextBox.h"
#include "RenderCombineText.h"
#include "TextPaintStyle.h"
#include <wtf/NeverDestroyed.h>
namespace WebCore {
TextPainter::TextPainter(GraphicsContext& context, bool paintSelectedTextOnly, bool paintSelectedTextSeparately, const FontCascade& font,
int startPositionInTextRun, int endPositionInTextBoxString, int length, const AtomicString& emphasisMark, RenderCombineText* combinedText, TextRun& textRun,
FloatRect& boxRect, FloatPoint& textOrigin, int emphasisMarkOffset, const ShadowData* textShadow, const ShadowData* selectionShadow,
bool textBoxIsHorizontal, TextPaintStyle& textPaintStyle, TextPaintStyle& selectionPaintStyle)
: m_context(context)
, m_textPaintStyle(textPaintStyle)
, m_selectionPaintStyle(selectionPaintStyle)
, m_textShadow(textShadow)
, m_selectionShadow(selectionShadow)
, m_paintSelectedTextOnly(paintSelectedTextOnly)
, m_paintSelectedTextSeparately(paintSelectedTextSeparately)
, m_font(font)
, m_startPositionInTextRun(startPositionInTextRun)
, m_endPositionInTextRun(endPositionInTextBoxString)
, m_length(length)
, m_emphasisMark(emphasisMark)
, m_combinedText(combinedText)
, m_textRun(textRun)
, m_boxRect(boxRect)
, m_textOrigin(textOrigin)
, m_emphasisMarkOffset(emphasisMarkOffset)
, m_textBoxIsHorizontal(textBoxIsHorizontal)
{
}
static void drawTextOrEmphasisMarks(GraphicsContext& context, const FontCascade& font, const TextRun& textRun, const AtomicString& emphasisMark,
int emphasisMarkOffset, const FloatPoint& point, const int from, const int to)
{
if (emphasisMark.isEmpty())
context.drawText(font, textRun, point, from, to);
else
context.drawEmphasisMarks(font, textRun, emphasisMark, point + IntSize(0, emphasisMarkOffset), from, to);
}
ShadowApplier::ShadowApplier(GraphicsContext& context, const ShadowData* shadow, const FloatRect& textRect, bool lastShadowIterationShouldDrawText, bool opaque, FontOrientation orientation)
: m_context(context)
, m_shadow(shadow)
, m_onlyDrawsShadow(!isLastShadowIteration() || !lastShadowIterationShouldDrawText)
, m_avoidDrawingShadow(shadowIsCompletelyCoveredByText(opaque))
, m_nothingToDraw(shadow && m_avoidDrawingShadow && m_onlyDrawsShadow)
, m_didSaveContext(false)
{
if (!shadow || m_nothingToDraw) {
m_shadow = nullptr;
return;
}
int shadowX = orientation == Horizontal ? shadow->x() : shadow->y();
int shadowY = orientation == Horizontal ? shadow->y() : -shadow->x();
FloatSize shadowOffset(shadowX, shadowY);
int shadowRadius = shadow->radius();
const Color& shadowColor = shadow->color();
if (m_onlyDrawsShadow) {
FloatRect shadowRect(textRect);
shadowRect.inflate(shadow->paintingExtent());
shadowRect.move(shadowOffset);
context.save();
context.clip(shadowRect);
m_didSaveContext = true;
m_extraOffset = FloatSize(0, 2 * textRect.height() + std::max(0.0f, shadowOffset.height()) + shadowRadius);
shadowOffset -= m_extraOffset;
}
if (!m_avoidDrawingShadow)
context.setShadow(shadowOffset, shadowRadius, shadowColor, context.fillColorSpace());
}
ShadowApplier::~ShadowApplier()
{
if (!m_shadow)
return;
if (m_onlyDrawsShadow)
m_context.restore();
else if (!m_avoidDrawingShadow)
m_context.clearShadow();
}
static void paintTextWithShadows(GraphicsContext& context, const FontCascade& font, const TextRun& textRun, const AtomicString& emphasisMark,
int emphasisMarkOffset, int startOffset, int endOffset, int truncationPoint, const FloatPoint& textOrigin, const FloatRect& boxRect,
const ShadowData* shadow, bool stroked, bool horizontal)
{
Color fillColor = context.fillColor();
ColorSpace fillColorSpace = context.fillColorSpace();
bool opaque = !fillColor.hasAlpha();
bool lastShadowIterationShouldDrawText = !stroked && opaque;
if (!opaque)
context.setFillColor(Color::black, fillColorSpace);
do {
ShadowApplier shadowApplier(context, shadow, boxRect, lastShadowIterationShouldDrawText, opaque, horizontal ? Horizontal : Vertical);
if (shadowApplier.nothingToDraw()) {
shadow = shadow->next();
continue;
}
IntSize extraOffset = roundedIntSize(shadowApplier.extraOffset());
if (!shadow && !opaque)
context.setFillColor(fillColor, fillColorSpace);
if (startOffset <= endOffset)
drawTextOrEmphasisMarks(context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, startOffset, endOffset);
else {
if (endOffset > 0)
drawTextOrEmphasisMarks(context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, 0, endOffset);
if (startOffset < truncationPoint)
drawTextOrEmphasisMarks(context, font, textRun, emphasisMark, emphasisMarkOffset, textOrigin + extraOffset, startOffset, truncationPoint);
}
if (!shadow)
break;
shadow = shadow->next();
} while (shadow || !lastShadowIterationShouldDrawText);
}
void TextPainter::paintText()
{
FloatPoint boxOrigin = m_boxRect.location();
if (!m_paintSelectedTextOnly) {
GraphicsContextStateSaver stateSaver(m_context, m_textPaintStyle.strokeWidth > 0);
updateGraphicsContext(m_context, m_textPaintStyle);
if (!m_paintSelectedTextSeparately || m_endPositionInTextRun <= m_startPositionInTextRun) {
paintTextWithShadows(m_context, m_font, m_textRun, nullAtom, 0, 0, m_length, m_length, m_textOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
} else
paintTextWithShadows(m_context, m_font, m_textRun, nullAtom, 0, m_endPositionInTextRun, m_startPositionInTextRun, m_length, m_textOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
if (!m_emphasisMark.isEmpty()) {
updateGraphicsContext(m_context, m_textPaintStyle, UseEmphasisMarkColor);
static NeverDestroyed<TextRun> objectReplacementCharacterTextRun(StringView(&objectReplacementCharacter, 1));
TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun.get() : m_textRun;
FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + m_boxRect.width() / 2, boxOrigin.y() + m_font.fontMetrics().ascent()) : m_textOrigin;
if (m_combinedText)
m_context.concatCTM(rotation(m_boxRect, Clockwise));
if (!m_paintSelectedTextSeparately || m_endPositionInTextRun <= m_startPositionInTextRun) {
paintTextWithShadows(m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, 0, m_length, m_length, emphasisMarkTextOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
} else
paintTextWithShadows(m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, m_endPositionInTextRun, m_startPositionInTextRun, m_length, emphasisMarkTextOrigin, m_boxRect, m_textShadow, m_textPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
if (m_combinedText)
m_context.concatCTM(rotation(m_boxRect, Counterclockwise));
}
}
if ((m_paintSelectedTextOnly || m_paintSelectedTextSeparately) && m_startPositionInTextRun < m_endPositionInTextRun) {
GraphicsContextStateSaver stateSaver(m_context, m_selectionPaintStyle.strokeWidth > 0);
updateGraphicsContext(m_context, m_selectionPaintStyle);
paintTextWithShadows(m_context, m_font, m_textRun, nullAtom, 0, m_startPositionInTextRun, m_endPositionInTextRun, m_length, m_textOrigin, m_boxRect, m_selectionShadow, m_selectionPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
if (!m_emphasisMark.isEmpty()) {
updateGraphicsContext(m_context, m_selectionPaintStyle, UseEmphasisMarkColor);
DEPRECATED_DEFINE_STATIC_LOCAL(TextRun, objectReplacementCharacterTextRun, (StringView(&objectReplacementCharacter, 1)));
TextRun& emphasisMarkTextRun = m_combinedText ? objectReplacementCharacterTextRun : m_textRun;
FloatPoint emphasisMarkTextOrigin = m_combinedText ? FloatPoint(boxOrigin.x() + m_boxRect.width() / 2, boxOrigin.y() + m_font.fontMetrics().ascent()) : m_textOrigin;
if (m_combinedText)
m_context.concatCTM(rotation(m_boxRect, Clockwise));
paintTextWithShadows(m_context, m_combinedText ? m_combinedText->originalFont() : m_font, emphasisMarkTextRun, m_emphasisMark, m_emphasisMarkOffset, m_startPositionInTextRun, m_endPositionInTextRun, m_length, emphasisMarkTextOrigin, m_boxRect, m_selectionShadow, m_selectionPaintStyle.strokeWidth > 0, m_textBoxIsHorizontal);
if (m_combinedText)
m_context.concatCTM(rotation(m_boxRect, Counterclockwise));
}
}
}
#if ENABLE(CSS3_TEXT_DECORATION_SKIP_INK)
DashArray TextPainter::dashesForIntersectionsWithRect(const FloatRect& lineExtents)
{
return m_font.dashesForIntersectionsWithRect(m_textRun, m_textOrigin, lineExtents);
}
#endif
}