blob: f1da479f26dbaa9720ef9492d28a033d244130ea [file] [log] [blame]
/*
* Copyright (C) 2020-2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "DrawGlyphsRecorder.h"
#include "BitmapImage.h"
#include "Color.h"
#include "FloatPoint.h"
#include "Font.h"
#include "FontCascade.h"
#include "FontPlatformData.h"
#include "GlyphBuffer.h"
#include "GraphicsContextCG.h"
#include "ImageBuffer.h"
#include <CoreText/CoreText.h>
#include <wtf/Vector.h>
#if PLATFORM(WIN)
#include <pal/spi/win/CoreTextSPIWin.h>
#endif
namespace WebCore {
static CGContextDelegateRef beginLayer(CGContextDelegateRef delegate, CGRenderingStateRef rstate, CGGStateRef gstate, CGRect rect, CFDictionaryRef, CGContextDelegateRef)
{
DrawGlyphsRecorder& recorder = *static_cast<DrawGlyphsRecorder*>(CGContextDelegateGetInfo(delegate));
recorder.recordBeginLayer(rstate, gstate, rect);
return delegate;
}
static CGContextDelegateRef endLayer(CGContextDelegateRef delegate, CGRenderingStateRef rstate, CGGStateRef gstate)
{
DrawGlyphsRecorder& recorder = *static_cast<DrawGlyphsRecorder*>(CGContextDelegateGetInfo(delegate));
recorder.recordEndLayer(rstate, gstate);
return delegate;
}
static CGError drawGlyphs(CGContextDelegateRef delegate, CGRenderingStateRef rstate, CGGStateRef gstate, const CGAffineTransform* tm, const CGGlyph glyphs[], const CGPoint positions[], size_t count)
{
if (CGGStateGetAlpha(gstate) > 0) {
DrawGlyphsRecorder& recorder = *static_cast<DrawGlyphsRecorder*>(CGContextDelegateGetInfo(delegate));
recorder.recordDrawGlyphs(rstate, gstate, tm, glyphs, positions, count);
}
return kCGErrorSuccess;
}
static CGError drawImage(CGContextDelegateRef delegate, CGRenderingStateRef rstate, CGGStateRef gstate, CGRect rect, CGImageRef image)
{
DrawGlyphsRecorder& recorder = *static_cast<DrawGlyphsRecorder*>(CGContextDelegateGetInfo(delegate));
recorder.recordDrawImage(rstate, gstate, rect, image);
return kCGErrorSuccess;
}
UniqueRef<GraphicsContext> DrawGlyphsRecorder::createInternalContext()
{
auto contextDelegate = adoptCF(CGContextDelegateCreate(this));
CGContextDelegateSetCallback(contextDelegate.get(), deBeginLayer, reinterpret_cast<CGContextDelegateCallback>(&beginLayer));
CGContextDelegateSetCallback(contextDelegate.get(), deEndLayer, reinterpret_cast<CGContextDelegateCallback>(&endLayer));
CGContextDelegateSetCallback(contextDelegate.get(), deDrawGlyphs, reinterpret_cast<CGContextDelegateCallback>(&WebCore::drawGlyphs));
CGContextDelegateSetCallback(contextDelegate.get(), deDrawImage, reinterpret_cast<CGContextDelegateCallback>(&drawImage));
#if HAVE(CORE_TEXT_FIX_FOR_RADAR_93925620)
auto contextType = kCGContextTypeUnknown;
#else
auto contextType = kCGContextTypeWindow;
#endif
auto context = adoptCF(CGContextCreateWithDelegate(contextDelegate.get(), contextType, nullptr, nullptr));
return makeUniqueRef<GraphicsContextCG>(context.get());
}
DrawGlyphsRecorder::DrawGlyphsRecorder(GraphicsContext& owner, float scaleFactor, DeriveFontFromContext deriveFontFromContext)
: m_owner(owner)
, m_internalContext(createInternalContext())
, m_deriveFontFromContext(deriveFontFromContext)
{
m_internalContext->applyDeviceScaleFactor(scaleFactor);
}
void DrawGlyphsRecorder::populateInternalState(const GraphicsContextState& contextState)
{
m_originalState.fillBrush = contextState.fillBrush();
m_originalState.strokeBrush = contextState.strokeBrush();
m_originalState.ctm = m_owner.getCTM();
m_originalState.dropShadow = contextState.dropShadow();
m_originalState.ignoreTransforms = contextState.shadowsIgnoreTransforms();
}
void DrawGlyphsRecorder::populateInternalContext(const GraphicsContextState& contextState)
{
m_internalContext->setCTM(m_originalState.ctm);
m_internalContext->setFillBrush(m_originalState.fillBrush);
m_internalContext->applyFillPattern();
m_internalContext->setStrokeBrush(m_originalState.strokeBrush);
m_internalContext->applyStrokePattern();
m_internalContext->setShadowsIgnoreTransforms(m_originalState.ignoreTransforms);
m_internalContext->setDropShadow(m_originalState.dropShadow);
m_internalContext->setTextDrawingMode(contextState.textDrawingMode());
}
void DrawGlyphsRecorder::recordInitialColors()
{
CGContextRef cgContext = m_internalContext->platformContext();
m_initialFillColor = CGContextGetFillColorAsColor(cgContext);
m_initialStrokeColor = CGContextGetStrokeColorAsColor(cgContext);
}
void DrawGlyphsRecorder::prepareInternalContext(const Font& font, FontSmoothingMode smoothingMode)
{
ASSERT(CGAffineTransformIsIdentity(CTFontGetMatrix(font.platformData().ctFont())));
m_originalFont = &font;
m_smoothingMode = smoothingMode;
m_originalTextMatrix = computeOverallTextMatrix(font);
if (font.platformData().orientation() == FontOrientation::Vertical)
m_originalTextMatrix = computeVerticalTextMatrix(font, m_originalTextMatrix);
auto& contextState = m_owner.state();
populateInternalState(contextState);
populateInternalContext(contextState);
recordInitialColors();
}
void DrawGlyphsRecorder::concludeInternalContext()
{
updateCTM(m_originalState.ctm);
updateFillBrush(m_originalState.fillBrush);
updateStrokeBrush(m_originalState.strokeBrush);
updateShadow(m_originalState.dropShadow, m_originalState.ignoreTransforms ? ShadowsIgnoreTransforms::Yes : ShadowsIgnoreTransforms::No);
}
void DrawGlyphsRecorder::updateFillColor(CGColorRef fillColor)
{
if (CGColorGetPattern(fillColor)) {
ASSERT(m_originalState.fillBrush.pattern());
return;
}
if (fillColor == m_initialFillColor)
m_owner.setFillBrush(m_originalState.fillBrush);
else
m_owner.setFillBrush(Color::createAndPreserveColorSpace(fillColor));
}
void DrawGlyphsRecorder::updateFillBrush(const SourceBrush& newBrush)
{
m_owner.setFillBrush(newBrush);
}
void DrawGlyphsRecorder::updateStrokeColor(CGColorRef strokeColor)
{
if (CGColorGetPattern(strokeColor)) {
ASSERT(m_originalState.strokeBrush.pattern());
return;
}
if (strokeColor == m_initialStrokeColor)
m_owner.setStrokeBrush(m_originalState.strokeBrush);
else
m_owner.setStrokeBrush(Color::createAndPreserveColorSpace(strokeColor));
}
void DrawGlyphsRecorder::updateStrokeBrush(const SourceBrush& newBrush)
{
m_owner.setStrokeBrush(newBrush);
}
void DrawGlyphsRecorder::updateCTM(const AffineTransform& ctm)
{
if (m_owner.getCTM() == ctm)
return;
// Instead of recording a SetCTM command, we compute the transform needed
// to change the current CTM to `ctm`. This allows the recorded comamnds
// to be re-used by elements drawing the same text in different locations.
if (auto inverseOfCurrentCTM = m_owner.getCTM().inverse())
m_owner.concatCTM(*inverseOfCurrentCTM * ctm);
}
void DrawGlyphsRecorder::updateShadow(const DropShadow& dropShadow, ShadowsIgnoreTransforms shadowsIgnoreTransforms)
{
m_owner.setDropShadow(dropShadow);
m_owner.setShadowsIgnoreTransforms(shadowsIgnoreTransforms == ShadowsIgnoreTransforms::Yes);
}
void DrawGlyphsRecorder::updateShadow(CGStyleRef style)
{
if (CGStyleGetType(style) != kCGStyleShadow) {
// FIXME: Support more kinds of CGStyles.
updateShadow({ }, ShadowsIgnoreTransforms::Unspecified);
return;
}
const auto& shadowStyle = *static_cast<const CGShadowStyle*>(CGStyleGetData(style));
auto rad = deg2rad(shadowStyle.azimuth - 180);
auto shadowOffset = FloatSize(std::cos(rad), std::sin(rad)) * shadowStyle.height;
auto shadowColor = CGStyleGetColor(style);
updateShadow({ shadowOffset, static_cast<float>(shadowStyle.radius), Color::createAndPreserveColorSpace(shadowColor) }, ShadowsIgnoreTransforms::Yes);
}
void DrawGlyphsRecorder::recordBeginLayer(CGRenderingStateRef, CGGStateRef gstate, CGRect)
{
updateCTM(*CGGStateGetCTM(gstate));
auto alpha = CGGStateGetAlpha(gstate);
m_owner.beginTransparencyLayer(alpha);
}
void DrawGlyphsRecorder::recordEndLayer(CGRenderingStateRef, CGGStateRef gstate)
{
updateCTM(*CGGStateGetCTM(gstate));
m_owner.endTransparencyLayer();
}
static Vector<CGSize> computeAdvancesFromPositions(const CGPoint positions[], size_t count, const CGAffineTransform& textMatrix)
{
Vector<CGSize> result;
for (size_t i = 0; i < count - 1; ++i) {
auto nextPosition = positions[i + 1];
auto currentPosition = positions[i];
auto advance = CGSizeMake(nextPosition.x - currentPosition.x, nextPosition.y - currentPosition.y);
result.append(CGSizeApplyAffineTransform(advance, textMatrix));
}
result.constructAndAppend(CGSizeMake(0, 0));
return result;
}
void DrawGlyphsRecorder::recordDrawGlyphs(CGRenderingStateRef, CGGStateRef gstate, const CGAffineTransform*, const CGGlyph glyphs[], const CGPoint positions[], size_t count)
{
ASSERT_IMPLIES(m_deriveFontFromContext == DeriveFontFromContext::No, m_originalFont);
if (!count)
return;
CGFontRef usedFont = CGGStateGetFont(gstate);
if (m_deriveFontFromContext == DeriveFontFromContext::No && usedFont != adoptCF(CTFontCopyGraphicsFont(m_originalFont->platformData().ctFont(), nullptr)).get())
return;
updateCTM(*CGGStateGetCTM(gstate));
// We want the replayer's CTM and text matrix to match the current CTM and text matrix.
// The current text matrix is a concatenation of whatever WebKit sets it to and whatever
// Core Text appends to it. So, we have
// CTM * m_originalTextMatrix * Core Text's text matrix.
// However, CGContextGetTextMatrix() just tells us what the whole text matrix is, so
// m_originalTextMatrix * Core Text's text matrix = currentTextMatrix.
// The only way we can emulate Core Text's text matrix is by modifying the CTM here.
// So, if we do that, the GPU process will have
// CTM * X * m_originalTextMatrix
// If you set these two equal to each other, and solve for X, you get
// CTM * currentTextMatrix = CTM * X * m_originalTextMatrix
// currentTextMatrix * inverse(m_originalTextMatrix) = X
AffineTransform currentTextMatrix = CGContextGetTextMatrix(m_internalContext->platformContext());
AffineTransform ctmFixup;
if (auto invertedOriginalTextMatrix = m_originalTextMatrix.inverse())
ctmFixup = currentTextMatrix * invertedOriginalTextMatrix.value();
AffineTransform inverseCTMFixup;
if (auto inverse = ctmFixup.inverse())
inverseCTMFixup = inverse.value();
else
ctmFixup = AffineTransform();
m_owner.concatCTM(ctmFixup);
updateFillColor(CGGStateGetFillColor(gstate));
updateStrokeColor(CGGStateGetStrokeColor(gstate));
updateShadow(CGGStateGetStyle(gstate));
auto fontSize = CGGStateGetFontSize(gstate);
Ref font = m_deriveFontFromContext == DeriveFontFromContext::No ? *m_originalFont : Font::create(FontPlatformData(adoptCF(CTFontCreateWithGraphicsFont(usedFont, fontSize, nullptr, nullptr)), fontSize));
// The above does the work of ensuring the right CTM (which is the combination of CG's CTM and
// CG's text matrix) is set for the replayer, but in order to provide the right values to
// `FontCascade::drawGlyphs` we need to recalculate the original advances from the resulting
// positions by inverting the operations applied to the original advances.
auto textMatrix = m_originalTextMatrix;
auto initialPenPosition = textMatrix.mapPoint(positions[0]);
if (font->platformData().orientation() == FontOrientation::Vertical) {
// Keep this in sync as the inverse of `fillVectorWithVerticalGlyphPositions`.
// FIXME: Use rotateLeftTransform(), as fillVectorWithVerticalGlyphPositions() does, instead of using transposedSize().
CGSize translation;
CTFontGetVerticalTranslationsForGlyphs(font->platformData().ctFont(), glyphs, &translation, 1);
initialPenPosition += FloatSize(translation).transposedSize();
auto ascentDelta = font->fontMetrics().floatAscent(IdeographicBaseline) - font->fontMetrics().floatAscent();
initialPenPosition.move(0, -ascentDelta);
}
m_owner.drawGlyphsAndCacheResources(font, glyphs, computeAdvancesFromPositions(positions, count, textMatrix).data(), count, initialPenPosition, m_smoothingMode);
m_owner.concatCTM(inverseCTMFixup);
}
void DrawGlyphsRecorder::recordDrawImage(CGRenderingStateRef, CGGStateRef gstate, CGRect rect, CGImageRef cgImage)
{
updateCTM(*CGGStateGetCTM(gstate));
updateShadow(CGGStateGetStyle(gstate));
// Core Graphics assumes a "y up" coordinate system, but in WebKit, we use a "y-down" coordinate system.
// This means that WebKit's drawing routines (GraphicsContext::drawImage()) intentionally draw images upside-down from Core Graphics's point of view.
// (There's a y-flip inside the implementation of GraphicsContext::drawImage().)
// The rect has the right bounds, but we need to transform from CG's coordinate system to WebKit's by performing our own y-flip so images are drawn the right-side-up.
// We do this at the boundary between the two APIs, which is right here.
m_owner.translate(0, rect.size.height + 2 * rect.origin.y);
m_owner.scale(FloatSize(1, -1));
auto image = NativeImage::create(cgImage);
m_owner.drawNativeImage(*image, image->size(), FloatRect(rect), FloatRect {{ }, image->size()}, ImagePaintingOptions { ImageOrientation::OriginTopLeft });
// Undo the above y-flip to restore the context.
m_owner.scale(FloatSize(1, -1));
m_owner.translate(0, -(rect.size.height + 2 * rect.origin.y));
}
void DrawGlyphsRecorder::drawOTSVGRun(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
{
FloatPoint penPosition = startPoint;
for (unsigned i = 0; i < numGlyphs; ++i) {
auto bounds = font.boundsForGlyph(glyphs[i]);
// Create a local ImageBuffer because decoding the SVG fonts has to happen in WebProcess.
if (auto imageBuffer = m_owner.createAlignedImageBuffer(bounds, DestinationColorSpace::SRGB(), RenderingMethod::Local)) {
FontCascade::drawGlyphs(imageBuffer->context(), font, glyphs + i, advances + i, 1, FloatPoint(), smoothingMode);
FloatRect destinationRect = enclosingIntRect(bounds);
destinationRect.moveBy(penPosition);
m_owner.drawImageBuffer(*imageBuffer, destinationRect);
}
penPosition.move(size(advances[i]));
}
}
void DrawGlyphsRecorder::drawNonOTSVGRun(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
{
prepareInternalContext(font, smoothingMode);
FontCascade::drawGlyphs(m_internalContext, font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
concludeInternalContext();
}
void DrawGlyphsRecorder::drawBySplittingIntoOTSVGAndNonOTSVGRuns(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
{
auto otsvgGlyphs = font.findOTSVGGlyphs(glyphs, numGlyphs);
if (!otsvgGlyphs) {
drawNonOTSVGRun(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
return;
}
ASSERT(otsvgGlyphs->size() >= numGlyphs);
// We can't just partition the glyphs into OT-SVG glyphs and non-OT-SVG glyphs because glyphs are allowed to draw outside of their layout boxes.
// This means that glyphs can overlap, which means we have to get the z-order correct. We can't have an earlier run be drawn on top of a later run.
FloatPoint runOrigin = startPoint;
FloatPoint penPosition = startPoint;
size_t glyphCountInRun = 0;
bool isOTSVGRun = false;
unsigned i;
auto draw = [&] () {
if (!glyphCountInRun)
return;
if (isOTSVGRun)
drawOTSVGRun(font, glyphs + i - glyphCountInRun, advances + i - glyphCountInRun, glyphCountInRun, runOrigin, smoothingMode);
else
drawNonOTSVGRun(font, glyphs + i - glyphCountInRun, advances + i - glyphCountInRun, glyphCountInRun, runOrigin, smoothingMode);
};
for (i = 0; i < numGlyphs; ++i) {
bool isOTSVGGlyph = otsvgGlyphs->quickGet(i);
if (isOTSVGGlyph != isOTSVGRun) {
draw();
isOTSVGRun = isOTSVGGlyph;
glyphCountInRun = 0;
runOrigin = penPosition;
}
++glyphCountInRun;
penPosition.move(size(advances[i]));
}
draw();
}
void DrawGlyphsRecorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
{
drawBySplittingIntoOTSVGAndNonOTSVGRuns(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
}
void DrawGlyphsRecorder::drawNativeText(CTFontRef font, CGFloat fontSize, CTLineRef line, CGRect lineRect)
{
GraphicsContextStateSaver ownerSaver(m_owner);
GraphicsContextStateSaver internalContextSaver(m_internalContext.get());
m_owner.translate(lineRect.origin.x, lineRect.origin.y + lineRect.size.height);
m_owner.scale(FloatSize(1, -1));
prepareInternalContext(Font::create(FontPlatformData(font, fontSize)), FontSmoothingMode::SubpixelAntialiased);
CGContextSetTextPosition(m_internalContext->platformContext(), 0, 0);
CTLineDraw(line, m_internalContext->platformContext());
concludeInternalContext();
}
} // namespace WebCore