blob: d43be6931caf11eae80b580ccb2d95a092059b46 [file] [log] [blame]
/*
* Copyright (C) 2020-2021 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 <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));
auto context = adoptCF(CGContextCreateWithDelegate(contextDelegate.get(), kCGContextTypeUnknown, nullptr, nullptr));
return makeUniqueRef<GraphicsContextCG>(context.get());
}
DrawGlyphsRecorder::DrawGlyphsRecorder(GraphicsContext& owner, DeconstructDrawGlyphs deconstructDrawGlyphs, DeriveFontFromContext deriveFontFromContext)
: m_owner(owner)
, m_deconstructDrawGlyphs(deconstructDrawGlyphs)
, m_deriveFontFromContext(deriveFontFromContext)
, m_internalContext(createInternalContext())
{
}
void DrawGlyphsRecorder::populateInternalState(const GraphicsContextState& contextState)
{
m_originalState.fillStyle.color = contextState.fillColor;
m_originalState.fillStyle.gradient = contextState.fillGradient;
m_originalState.fillStyle.gradientSpaceTransform = contextState.fillGradientSpaceTransform;
m_originalState.fillStyle.pattern = contextState.fillPattern;
m_originalState.strokeStyle.color = contextState.strokeColor;
m_originalState.strokeStyle.gradient = contextState.strokeGradient;
m_originalState.strokeStyle.gradientSpaceTransform = contextState.strokeGradientSpaceTransform;
m_originalState.strokeStyle.pattern = contextState.strokePattern;
m_originalState.ctm = m_owner.getCTM(); // FIXME: Deal with base CTM.
m_originalState.shadow.offset = contextState.shadowOffset;
m_originalState.shadow.blur = contextState.shadowBlur;
m_originalState.shadow.color = contextState.shadowColor;
m_originalState.shadow.ignoreTransforms = contextState.shadowsIgnoreTransforms;
m_currentState = m_originalState;
}
void DrawGlyphsRecorder::populateInternalContext(const GraphicsContextState& contextState)
{
if (m_originalState.fillStyle.color.isValid())
m_internalContext->setFillColor(m_originalState.fillStyle.color);
else if (m_originalState.fillStyle.gradient)
m_internalContext->setFillGradient(*m_originalState.fillStyle.gradient, m_originalState.fillStyle.gradientSpaceTransform);
else {
ASSERT(m_originalState.fillStyle.pattern);
if (m_originalState.fillStyle.pattern)
m_internalContext->setFillPattern(*m_originalState.fillStyle.pattern);
}
if (m_originalState.strokeStyle.color.isValid())
m_internalContext->setStrokeColor(m_originalState.strokeStyle.color);
else if (m_originalState.strokeStyle.gradient)
m_internalContext->setStrokeGradient(*m_originalState.strokeStyle.gradient, m_originalState.strokeStyle.gradientSpaceTransform);
else {
ASSERT(m_originalState.strokeStyle.pattern);
if (m_originalState.strokeStyle.pattern)
m_internalContext->setStrokePattern(*m_originalState.strokeStyle.pattern);
}
m_internalContext->setCTM(m_originalState.ctm);
m_internalContext->setShadowsIgnoreTransforms(m_originalState.shadow.ignoreTransforms);
m_internalContext->setShadow(m_originalState.shadow.offset, m_originalState.shadow.blur, m_originalState.shadow.color, contextState.shadowRadiusMode);
m_internalContext->setTextDrawingMode(contextState.textDrawingMode);
}
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);
}
void DrawGlyphsRecorder::concludeInternalContext()
{
updateCTM(m_originalState.ctm);
updateFillColor(m_originalState.fillStyle.color, m_originalState.fillStyle.gradient.get(), m_originalState.fillStyle.pattern.get());
updateStrokeColor(m_originalState.strokeStyle.color, m_originalState.strokeStyle.gradient.get(), m_originalState.strokeStyle.pattern.get());
updateShadow(m_originalState.shadow.offset, m_originalState.shadow.blur, m_originalState.shadow.color, m_originalState.shadow.ignoreTransforms ? ShadowsIgnoreTransforms::Yes : ShadowsIgnoreTransforms::No);
}
void DrawGlyphsRecorder::updateFillColor(const Color& newColor, Gradient* newGradient, Pattern* newPattern)
{
// This check looks wrong but it actually isn't, for our limited use.
// CT will only ever set this to a solid color, which this is the correct check for.
// In concludeInternalContext() we set it back to what it was originally, which this check works correctly for too.
if (newColor == m_currentState.fillStyle.color)
return;
GraphicsContextState newState;
newState.fillColor = newColor;
if (newGradient)
newState.fillGradient = newGradient;
if (newPattern)
newState.fillPattern = newPattern;
m_owner.didUpdateState(newState, { GraphicsContextState::FillColorChange });
m_currentState.fillStyle.color = newColor;
}
void DrawGlyphsRecorder::updateStrokeColor(const Color& newColor, Gradient* newGradient, Pattern* newPattern)
{
// This check looks wrong but it actually isn't, for our limited use.
// CT will only ever set this to a solid color, which this is the correct check for.
// In concludeInternalContext() we set it back to what it was originally, which this check works correctly for too.
if (newColor == m_currentState.strokeStyle.color)
return;
GraphicsContextState newState;
newState.strokeColor = newColor;
if (newGradient)
newState.strokeGradient = newGradient;
if (newPattern)
newState.strokePattern = newPattern;
m_owner.didUpdateState(newState, { GraphicsContextState::StrokeColorChange });
m_currentState.strokeStyle.color = newColor;
}
void DrawGlyphsRecorder::updateCTM(const AffineTransform& ctm)
{
if (ctm == m_currentState.ctm)
return;
m_owner.setCTM(ctm);
m_currentState.ctm = ctm;
}
static bool shadowIsCleared(const FloatSize& shadowOffset, float shadowBlur)
{
return shadowOffset == FloatSize() && !shadowBlur;
}
void DrawGlyphsRecorder::updateShadow(const FloatSize& shadowOffset, float shadowBlur, const Color& shadowColor, ShadowsIgnoreTransforms shadowsIgnoreTransforms)
{
// We don't need to consider shadowsIgnoreTransforms if nobody has any shadows.
if (shadowIsCleared(shadowOffset, shadowBlur) && shadowIsCleared(m_currentState.shadow.offset, m_currentState.shadow.blur))
return;
GraphicsContextState newState;
GraphicsContextState::StateChangeFlags stateChangeFlags;
if (shadowOffset != m_currentState.shadow.offset || shadowBlur != m_currentState.shadow.blur || shadowColor != m_currentState.shadow.color) {
newState.shadowOffset = shadowOffset;
newState.shadowBlur = shadowBlur;
newState.shadowColor = shadowColor;
stateChangeFlags.add(GraphicsContextState::ShadowChange);
}
if (shadowsIgnoreTransforms != ShadowsIgnoreTransforms::Unspecified && (shadowsIgnoreTransforms == ShadowsIgnoreTransforms::Yes) != m_currentState.shadow.ignoreTransforms) {
newState.shadowsIgnoreTransforms = (shadowsIgnoreTransforms == ShadowsIgnoreTransforms::Yes);
stateChangeFlags.add(GraphicsContextState::ShadowsIgnoreTransformsChange);
}
if (stateChangeFlags.isEmpty())
return;
m_owner.didUpdateState(newState, stateChangeFlags);
m_currentState.shadow.offset = shadowOffset;
m_currentState.shadow.blur = shadowBlur;
m_currentState.shadow.color = shadowColor;
m_currentState.shadow.ignoreTransforms = (shadowsIgnoreTransforms == ShadowsIgnoreTransforms::Yes);
}
void DrawGlyphsRecorder::updateShadow(CGStyleRef style)
{
if (CGStyleGetType(style) != kCGStyleShadow) {
// FIXME: Support more kinds of CGStyles.
updateShadow({0, 0}, 0, Color(), 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, 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);
auto fillColor = CGGStateGetFillColor(gstate);
auto strokeColor = CGGStateGetStrokeColor(gstate);
updateFillColor(Color::createAndPreserveColorSpace(fillColor));
updateStrokeColor(Color::createAndPreserveColorSpace(strokeColor));
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;
if (font->platformData().orientation() == FontOrientation::Vertical) {
// Keep this in sync as the inverse of `fillVectorWithVerticalGlyphPositions`.
// FIXME: <https://webkit.org/b/232917> (`DrawGlyphsRecorder` should be able to record+replay vertical text)
}
m_owner.drawGlyphsAndCacheFont(font, glyphs, computeAdvancesFromPositions(positions, count, textMatrix).data(), count, textMatrix.mapPoint(positions[0]), 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));
}
struct GlyphsAndAdvancesStorage {
Vector<GlyphBufferGlyph> glyphs;
Vector<GlyphBufferAdvance> advances;
};
struct GlyphsAndAdvances {
const GlyphBufferGlyph* glyphs;
const GlyphBufferAdvance* advances;
unsigned numGlyphs;
GlyphBufferAdvance initialAdvance;
std::optional<GlyphsAndAdvancesStorage> storage;
};
static GlyphsAndAdvances filterOutOTSVGGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs)
{
auto otsvgGlyphs = font.findOTSVGGlyphs(glyphs, numGlyphs);
if (!otsvgGlyphs)
return { glyphs, advances, numGlyphs, makeGlyphBufferAdvance(), { }};
ASSERT(otsvgGlyphs->size() >= numGlyphs);
GlyphsAndAdvances result;
result.initialAdvance = makeGlyphBufferAdvance();
result.storage = GlyphsAndAdvancesStorage();
result.storage->glyphs.reserveInitialCapacity(numGlyphs);
result.storage->advances.reserveInitialCapacity(numGlyphs);
for (unsigned i = 0; i < numGlyphs; ++i) {
ASSERT(result.storage->glyphs.size() == result.storage->advances.size());
if (otsvgGlyphs->quickGet(i)) {
if (result.storage->advances.isEmpty())
result.initialAdvance = makeGlyphBufferAdvance(size(result.initialAdvance) + size(advances[i]));
else
result.storage->advances.last() = makeGlyphBufferAdvance(size(result.storage->advances.last()) + size(advances[i]));
} else {
result.storage->glyphs.uncheckedAppend(glyphs[i]);
result.storage->advances.uncheckedAppend(advances[i]);
}
ASSERT(result.storage->glyphs.size() == result.storage->advances.size());
}
result.glyphs = result.storage->glyphs.data();
result.advances = result.storage->advances.data();
result.numGlyphs = result.storage->glyphs.size();
return result;
}
void DrawGlyphsRecorder::drawGlyphs(const Font& font, const GlyphBufferGlyph* glyphs, const GlyphBufferAdvance* advances, unsigned numGlyphs, const FloatPoint& startPoint, FontSmoothingMode smoothingMode)
{
if (m_deconstructDrawGlyphs == DeconstructDrawGlyphs::No) {
m_owner.drawGlyphsAndCacheFont(font, glyphs, advances, numGlyphs, startPoint, smoothingMode);
return;
}
ASSERT(m_deconstructDrawGlyphs == DeconstructDrawGlyphs::Yes);
// FIXME: <rdar://problem/70166552> Record OTSVG glyphs.
GlyphsAndAdvances glyphsAndAdvancesWithoutOTSVGGlyphs = filterOutOTSVGGlyphs(font, glyphs, advances, numGlyphs);
ASSERT(glyphsAndAdvancesWithoutOTSVGGlyphs.glyphs == glyphs || glyphsAndAdvancesWithoutOTSVGGlyphs.glyphs == glyphsAndAdvancesWithoutOTSVGGlyphs.storage->glyphs.data());
ASSERT(glyphsAndAdvancesWithoutOTSVGGlyphs.advances == advances || glyphsAndAdvancesWithoutOTSVGGlyphs.advances == glyphsAndAdvancesWithoutOTSVGGlyphs.storage->advances.data());
prepareInternalContext(font, smoothingMode);
FontCascade::drawGlyphs(m_internalContext, font, glyphsAndAdvancesWithoutOTSVGGlyphs.glyphs, glyphsAndAdvancesWithoutOTSVGGlyphs.advances, glyphsAndAdvancesWithoutOTSVGGlyphs.numGlyphs, startPoint + size(glyphsAndAdvancesWithoutOTSVGGlyphs.initialAdvance), smoothingMode);
concludeInternalContext();
}
void DrawGlyphsRecorder::drawNativeText(CTFontRef font, CGFloat fontSize, CTLineRef line, CGRect lineRect)
{
ASSERT(m_deconstructDrawGlyphs == DeconstructDrawGlyphs::Yes);
GraphicsContextStateSaver saver(m_owner);
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