blob: d85eb5c5fe73e7e71d2493887ed1b130bbdf9966 [file] [log] [blame]
/*
* Copyright 2006 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkTypes.h"
#if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
#ifdef SK_BUILD_FOR_MAC
#import <ApplicationServices/ApplicationServices.h>
#endif
#ifdef SK_BUILD_FOR_IOS
#include <CoreText/CoreText.h>
#include <CoreText/CTFontManager.h>
#include <CoreGraphics/CoreGraphics.h>
#include <CoreFoundation/CoreFoundation.h>
#endif
#include "include/core/SkColor.h"
#include "include/core/SkColorPriv.h"
#include "include/core/SkFontMetrics.h"
#include "include/core/SkFontTypes.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkTypeface.h"
#include "include/private/SkColorData.h"
#include "include/private/SkFixed.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkTo.h"
#include "src/core/SkAutoMalloc.h"
#include "src/core/SkEndian.h"
#include "src/core/SkGlyph.h"
#include "src/core/SkMask.h"
#include "src/core/SkMaskGamma.h"
#include "src/core/SkMathPriv.h"
#include "src/core/SkOpts.h"
#include "src/ports/SkScalerContext_mac_ct.h"
#include "src/ports/SkTypeface_mac_ct.h"
#include "src/sfnt/SkOTTableTypes.h"
#include "src/sfnt/SkOTTable_OS_2.h"
#include "src/utils/mac/SkCGBase.h"
#include "src/utils/mac/SkCGGeometry.h"
#include "src/utils/mac/SkCTFont.h"
#include "src/utils/mac/SkUniqueCFRef.h"
#include <algorithm>
class SkDescriptor;
namespace {
static inline const constexpr bool kSkShowTextBlitCoverage = false;
}
static void sk_memset_rect32(uint32_t* ptr, uint32_t value,
int width, int height, size_t rowBytes) {
SkASSERT(width);
SkASSERT(width * sizeof(uint32_t) <= rowBytes);
if (width >= 32) {
while (height) {
sk_memset32(ptr, value, width);
ptr = (uint32_t*)((char*)ptr + rowBytes);
height -= 1;
}
return;
}
rowBytes -= width * sizeof(uint32_t);
if (width >= 8) {
while (height) {
int w = width;
do {
*ptr++ = value; *ptr++ = value;
*ptr++ = value; *ptr++ = value;
*ptr++ = value; *ptr++ = value;
*ptr++ = value; *ptr++ = value;
w -= 8;
} while (w >= 8);
while (--w >= 0) {
*ptr++ = value;
}
ptr = (uint32_t*)((char*)ptr + rowBytes);
height -= 1;
}
} else {
while (height) {
int w = width;
do {
*ptr++ = value;
} while (--w > 0);
ptr = (uint32_t*)((char*)ptr + rowBytes);
height -= 1;
}
}
}
static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) {
return pixel & 0xFF;
}
static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix) {
return CGAffineTransformMake( SkScalarToCGFloat(matrix[SkMatrix::kMScaleX]),
-SkScalarToCGFloat(matrix[SkMatrix::kMSkewY] ),
-SkScalarToCGFloat(matrix[SkMatrix::kMSkewX] ),
SkScalarToCGFloat(matrix[SkMatrix::kMScaleY]),
SkScalarToCGFloat(matrix[SkMatrix::kMTransX]),
SkScalarToCGFloat(matrix[SkMatrix::kMTransY]));
}
SkScalerContext_Mac::SkScalerContext_Mac(sk_sp<SkTypeface_Mac> typeface,
const SkScalerContextEffects& effects,
const SkDescriptor* desc)
: INHERITED(std::move(typeface), effects, desc)
, fOffscreen(fRec.fForegroundColor)
, fDoSubPosition(SkToBool(fRec.fFlags & kSubpixelPositioning_Flag))
{
CTFontRef ctFont = (CTFontRef)this->getTypeface()->internal_private_getCTFontRef();
// CT on (at least) 10.9 will size color glyphs down from the requested size, but not up.
// As a result, it is necessary to know the actual device size and request that.
SkVector scale;
SkMatrix skTransform;
bool invertible = fRec.computeMatrices(SkScalerContextRec::PreMatrixScale::kVertical,
&scale, &skTransform, nullptr, nullptr, nullptr);
fTransform = MatrixToCGAffineTransform(skTransform);
// CGAffineTransformInvert documents that if the transform is non-invertible it will return the
// passed transform unchanged. It does so, but then also prints a message to stdout. Avoid this.
if (invertible) {
fInvTransform = CGAffineTransformInvert(fTransform);
} else {
fInvTransform = fTransform;
}
// The transform contains everything except the requested text size.
// Some properties, like 'trak', are based on the optical text size.
CGFloat textSize = SkScalarToCGFloat(scale.y());
fCTFont = SkCTFontCreateExactCopy(ctFont, textSize,
((SkTypeface_Mac*)this->getTypeface())->fOpszVariation);
fCGFont.reset(CTFontCopyGraphicsFont(fCTFont.get(), nullptr));
}
static int RoundSize(int dimension) {
return SkNextPow2(dimension);
}
static CGColorRef CGColorForSkColor(CGColorSpaceRef rgbcs, SkColor bgra) {
CGFloat components[4];
components[0] = (CGFloat)SkColorGetR(bgra) * (1/255.0f);
components[1] = (CGFloat)SkColorGetG(bgra) * (1/255.0f);
components[2] = (CGFloat)SkColorGetB(bgra) * (1/255.0f);
// CoreText applies the CGContext fill color as the COLR foreground color.
// However, the alpha is applied to the whole glyph drawing (and Skia will do that as well).
// For now, cannot really support COLR foreground color alpha.
components[3] = 1.0f;
return CGColorCreate(rgbcs, components);
}
SkScalerContext_Mac::Offscreen::Offscreen(SkColor foregroundColor)
: fCG(nullptr)
, fSKForegroundColor(foregroundColor)
, fDoAA(false)
, fDoLCD(false)
{
fSize.set(0, 0);
}
CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& context,
const SkGlyph& glyph, CGGlyph glyphID,
size_t* rowBytesPtr,
bool generateA8FromLCD) {
if (!fRGBSpace) {
//It doesn't appear to matter what color space is specified.
//Regular blends and antialiased text are always (s*a + d*(1-a))
//and subpixel antialiased text is always g=2.0.
fRGBSpace.reset(CGColorSpaceCreateDeviceRGB());
fCGForegroundColor.reset(CGColorForSkColor(fRGBSpace.get(), fSKForegroundColor));
}
// default to kBW_Format
bool doAA = false;
bool doLCD = false;
if (SkMask::kBW_Format != glyph.maskFormat()) {
doLCD = true;
doAA = true;
}
// FIXME: lcd smoothed un-hinted rasterization unsupported.
if (!generateA8FromLCD && SkMask::kA8_Format == glyph.maskFormat()) {
doLCD = false;
doAA = true;
}
// If this font might have color glyphs, disable LCD as there's no way to support it.
// CoreText doesn't tell us which format it ended up using, so we can't detect it.
// A8 will end up black on transparent, but TODO: we can detect gray and set to A8.
if (SkMask::kARGB32_Format == glyph.maskFormat()) {
doLCD = false;
}
size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
if (!fCG || fSize.fWidth < glyph.width() || fSize.fHeight < glyph.height()) {
if (fSize.fWidth < glyph.width()) {
fSize.fWidth = RoundSize(glyph.width());
}
if (fSize.fHeight < glyph.height()) {
fSize.fHeight = RoundSize(glyph.height());
}
rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
void* image = fImageStorage.reset(rowBytes * fSize.fHeight);
const CGImageAlphaInfo alpha = (glyph.isColor())
? kCGImageAlphaPremultipliedFirst
: kCGImageAlphaNoneSkipFirst;
const CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (CGBitmapInfo)alpha;
fCG.reset(CGBitmapContextCreate(image, fSize.fWidth, fSize.fHeight, 8,
rowBytes, fRGBSpace.get(), bitmapInfo));
// Skia handles quantization and subpixel positioning,
// so disable quantization and enable subpixel positioning in CG.
CGContextSetAllowsFontSubpixelQuantization(fCG.get(), false);
CGContextSetShouldSubpixelQuantizeFonts(fCG.get(), false);
// Because CG always draws from the horizontal baseline,
// if there is a non-integral translation from the horizontal origin to the vertical origin,
// then CG cannot draw the glyph in the correct location without subpixel positioning.
CGContextSetAllowsFontSubpixelPositioning(fCG.get(), true);
CGContextSetShouldSubpixelPositionFonts(fCG.get(), true);
CGContextSetTextDrawingMode(fCG.get(), kCGTextFill);
if (SkMask::kARGB32_Format != glyph.maskFormat()) {
// Draw black on white to create mask. (Special path exists to speed this up in CG.)
CGContextSetGrayFillColor(fCG.get(), 0.0f, 1.0f);
} else {
CGContextSetFillColorWithColor(fCG.get(), fCGForegroundColor.get());
}
// force our checks below to happen
fDoAA = !doAA;
fDoLCD = !doLCD;
CGContextSetTextMatrix(fCG.get(), context.fTransform);
}
if (fDoAA != doAA) {
CGContextSetShouldAntialias(fCG.get(), doAA);
fDoAA = doAA;
}
if (fDoLCD != doLCD) {
CGContextSetShouldSmoothFonts(fCG.get(), doLCD);
fDoLCD = doLCD;
}
CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get();
// skip rows based on the glyph's height
image += (fSize.fHeight - glyph.height()) * fSize.fWidth;
// Erase to white (or transparent black if it's a color glyph, to not composite against white).
uint32_t bgColor = (!glyph.isColor()) ? 0xFFFFFFFF : 0x00000000;
sk_memset_rect32(image, bgColor, glyph.width(), glyph.height(), rowBytes);
float subX = 0;
float subY = 0;
if (context.fDoSubPosition) {
subX = SkFixedToFloat(glyph.getSubXFixed());
subY = SkFixedToFloat(glyph.getSubYFixed());
}
CGPoint point = CGPointMake(-glyph.left() + subX, glyph.top() + glyph.height() - subY);
// Prior to 10.10, CTFontDrawGlyphs acted like CGContextShowGlyphsAtPositions and took
// 'positions' which are in text space. The glyph location (in device space) must be
// mapped into text space, so that CG can convert it back into device space.
// In 10.10.1, this is handled directly in CTFontDrawGlyphs.
//
// However, in 10.10.2 color glyphs no longer rotate based on the font transform.
// So always make the font transform identity and place the transform on the context.
point = CGPointApplyAffineTransform(point, context.fInvTransform);
CTFontDrawGlyphs(context.fCTFont.get(), &glyphID, &point, 1, fCG.get());
SkASSERT(rowBytesPtr);
*rowBytesPtr = rowBytes;
return image;
}
bool SkScalerContext_Mac::generateAdvance(SkGlyph* glyph) {
return false;
}
void SkScalerContext_Mac::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
glyph->fMaskFormat = fRec.fMaskFormat;
if (((SkTypeface_Mac*)this->getTypeface())->fHasColorGlyphs) {
glyph->setPath(alloc, nullptr, false);
}
const CGGlyph cgGlyph = (CGGlyph) glyph->getGlyphID();
glyph->zeroMetrics();
// The following block produces cgAdvance in CG units (pixels, y up).
CGSize cgAdvance;
CTFontGetAdvancesForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
&cgGlyph, &cgAdvance, 1);
cgAdvance = CGSizeApplyAffineTransform(cgAdvance, fTransform);
glyph->fAdvanceX = SkFloatFromCGFloat(cgAdvance.width);
glyph->fAdvanceY = -SkFloatFromCGFloat(cgAdvance.height);
// The following produces skBounds in SkGlyph units (pixels, y down),
// or returns early if skBounds would be empty.
SkRect skBounds;
// Glyphs are always drawn from the horizontal origin. The caller must manually use the result
// of CTFontGetVerticalTranslationsForGlyphs to calculate where to draw the glyph for vertical
// glyphs. As a result, always get the horizontal bounds of a glyph and translate it if the
// glyph is vertical. This avoids any diagreement between the various means of retrieving
// vertical metrics.
{
// CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up).
CGRect cgBounds;
CTFontGetBoundingRectsForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
&cgGlyph, &cgBounds, 1);
cgBounds = CGRectApplyAffineTransform(cgBounds, fTransform);
// BUG?
// 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when
// it should be empty. So, if we see a zero-advance, we check if it has an
// empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance
// is rare, so we won't incur a big performance cost for this extra check.
if (0 == cgAdvance.width && 0 == cgAdvance.height) {
SkUniqueCFRef<CGPathRef> path(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph,nullptr));
if (!path || CGPathIsEmpty(path.get())) {
return;
}
}
if (SkCGRectIsEmpty(cgBounds)) {
return;
}
// Convert cgBounds to SkGlyph units (pixels, y down).
skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height,
cgBounds.size.width, cgBounds.size.height);
}
// Currently the bounds are based on being rendered at (0,0).
// The top left must not move, since that is the base from which subpixel positioning is offset.
if (fDoSubPosition) {
skBounds.fRight += SkFixedToFloat(glyph->getSubXFixed());
skBounds.fBottom += SkFixedToFloat(glyph->getSubYFixed());
}
// We're trying to pack left and top into int16_t,
// and width and height into uint16_t, after outsetting by 1.
if (!SkRect::MakeXYWH(-32767, -32767, 65535, 65535).contains(skBounds)) {
return;
}
SkIRect skIBounds;
skBounds.roundOut(&skIBounds);
// Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
// Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
// is not currently known, as CG dilates the outlines by some percentage.
// Note that if this context is A8 and not back-forming from LCD, there is no need to outset.
skIBounds.outset(1, 1);
glyph->fLeft = SkToS16(skIBounds.fLeft);
glyph->fTop = SkToS16(skIBounds.fTop);
glyph->fWidth = SkToU16(skIBounds.width());
glyph->fHeight = SkToU16(skIBounds.height());
}
static constexpr uint8_t sk_pow2_table(size_t i) {
return SkToU8(((i * i + 128) / 255));
}
/**
* This will invert the gamma applied by CoreGraphics, so we can get linear
* values.
*
* CoreGraphics obscurely defaults to 2.0 as the subpixel coverage gamma value.
* The color space used does not appear to affect this choice.
*/
static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table);
static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) {
while (count > 0) {
uint8_t mask = 0;
for (int i = 7; i >= 0; --i) {
mask |= ((CGRGBPixel_getAlpha(*src++) >> 7) ^ 0x1) << i;
if (0 == --count) {
break;
}
}
*dst++ = mask;
}
}
template<bool APPLY_PREBLEND>
static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) {
U8CPU r = 0xFF - ((rgb >> 16) & 0xFF);
U8CPU g = 0xFF - ((rgb >> 8) & 0xFF);
U8CPU b = 0xFF - ((rgb >> 0) & 0xFF);
U8CPU lum = sk_apply_lut_if<APPLY_PREBLEND>(SkComputeLuminance(r, g, b), table8);
if constexpr (kSkShowTextBlitCoverage) {
lum = std::max(lum, (U8CPU)0x30);
}
return lum;
}
template<bool APPLY_PREBLEND>
static void RGBToA8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
const SkGlyph& glyph, void* glyphImage, const uint8_t* table8) {
const int width = glyph.width();
const int height = glyph.height();
size_t dstRB = glyph.rowBytes();
uint8_t* SK_RESTRICT dst = (uint8_t*)glyphImage;
for (int y = 0; y < height; y++) {
for (int i = 0; i < width; ++i) {
dst[i] = rgb_to_a8<APPLY_PREBLEND>(cgPixels[i], table8);
}
cgPixels = SkTAddOffset<const CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint8_t>(dst, dstRB);
}
}
template<bool APPLY_PREBLEND>
static uint16_t RGBToLcd16(CGRGBPixel rgb,
const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 16) & 0xFF), tableR);
U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 8) & 0xFF), tableG);
U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 0) & 0xFF), tableB);
if constexpr (kSkShowTextBlitCoverage) {
r = std::max(r, (U8CPU)0x30);
g = std::max(g, (U8CPU)0x30);
b = std::max(b, (U8CPU)0x30);
}
return SkPack888ToRGB16(r, g, b);
}
template<bool APPLY_PREBLEND>
static void RGBToLcd16(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
const SkGlyph& glyph, void* glyphImage,
const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
const int width = glyph.width();
const int height = glyph.height();
size_t dstRB = glyph.rowBytes();
uint16_t* SK_RESTRICT dst = (uint16_t*)glyphImage;
for (int y = 0; y < height; y++) {
for (int i = 0; i < width; i++) {
dst[i] = RGBToLcd16<APPLY_PREBLEND>(cgPixels[i], tableR, tableG, tableB);
}
cgPixels = SkTAddOffset<const CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint16_t>(dst, dstRB);
}
}
static SkPMColor cgpixels_to_pmcolor(CGRGBPixel rgb) {
U8CPU a = (rgb >> 24) & 0xFF;
U8CPU r = (rgb >> 16) & 0xFF;
U8CPU g = (rgb >> 8) & 0xFF;
U8CPU b = (rgb >> 0) & 0xFF;
if constexpr (kSkShowTextBlitCoverage) {
a = std::max(a, (U8CPU)0x30);
}
return SkPackARGB32(a, r, g, b);
}
void SkScalerContext_Mac::generateImage(const SkGlyph& glyph) {
CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
// FIXME: lcd smoothed un-hinted rasterization unsupported.
bool requestSmooth = fRec.getHinting() != SkFontHinting::kNone;
// Draw the glyph
size_t cgRowBytes;
CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth);
if (cgPixels == nullptr) {
return;
}
// Fix the glyph
if ((glyph.fMaskFormat == SkMask::kLCD16_Format) ||
(glyph.fMaskFormat == SkMask::kA8_Format
&& requestSmooth
&& SkCTFontGetSmoothBehavior() != SkCTFontSmoothBehavior::none))
{
const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
//Note that the following cannot really be integrated into the
//pre-blend, since we may not be applying the pre-blend; when we aren't
//applying the pre-blend it means that a filter wants linear anyway.
//Other code may also be applying the pre-blend, so we'd need another
//one with this and one without.
CGRGBPixel* addr = cgPixels;
for (int y = 0; y < glyph.fHeight; ++y) {
for (int x = 0; x < glyph.fWidth; ++x) {
int r = (addr[x] >> 16) & 0xFF;
int g = (addr[x] >> 8) & 0xFF;
int b = (addr[x] >> 0) & 0xFF;
addr[x] = (linear[r] << 16) | (linear[g] << 8) | linear[b];
}
addr = SkTAddOffset<CGRGBPixel>(addr, cgRowBytes);
}
}
// Convert glyph to mask
switch (glyph.fMaskFormat) {
case SkMask::kLCD16_Format: {
if (fPreBlend.isApplicable()) {
RGBToLcd16<true>(cgPixels, cgRowBytes, glyph, glyph.fImage,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
RGBToLcd16<false>(cgPixels, cgRowBytes, glyph, glyph.fImage,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} break;
case SkMask::kA8_Format: {
if (fPreBlend.isApplicable()) {
RGBToA8<true>(cgPixels, cgRowBytes, glyph, glyph.fImage, fPreBlend.fG);
} else {
RGBToA8<false>(cgPixels, cgRowBytes, glyph, glyph.fImage, fPreBlend.fG);
}
} break;
case SkMask::kBW_Format: {
const int width = glyph.fWidth;
size_t dstRB = glyph.rowBytes();
uint8_t* dst = (uint8_t*)glyph.fImage;
for (int y = 0; y < glyph.fHeight; y++) {
cgpixels_to_bits(dst, cgPixels, width);
cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<uint8_t>(dst, dstRB);
}
} break;
case SkMask::kARGB32_Format: {
const int width = glyph.fWidth;
size_t dstRB = glyph.rowBytes();
SkPMColor* dst = (SkPMColor*)glyph.fImage;
for (int y = 0; y < glyph.fHeight; y++) {
for (int x = 0; x < width; ++x) {
dst[x] = cgpixels_to_pmcolor(cgPixels[x]);
}
cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
dst = SkTAddOffset<SkPMColor>(dst, dstRB);
}
} break;
default:
SkDEBUGFAIL("unexpected mask format");
break;
}
}
namespace {
class SkCTPathGeometrySink {
SkPathBuilder fBuilder;
bool fStarted;
CGPoint fCurrent;
void goingTo(const CGPoint pt) {
if (!fStarted) {
fStarted = true;
fBuilder.moveTo(fCurrent.x, -fCurrent.y);
}
fCurrent = pt;
}
bool currentIsNot(const CGPoint pt) {
return fCurrent.x != pt.x || fCurrent.y != pt.y;
}
public:
SkCTPathGeometrySink() : fStarted{false}, fCurrent{0,0} {}
SkPath detach() { return fBuilder.detach(); }
static void ApplyElement(void *ctx, const CGPathElement *element) {
SkCTPathGeometrySink& self = *(SkCTPathGeometrySink*)ctx;
CGPoint* points = element->points;
switch (element->type) {
case kCGPathElementMoveToPoint:
self.fStarted = false;
self.fCurrent = points[0];
break;
case kCGPathElementAddLineToPoint:
if (self.currentIsNot(points[0])) {
self.goingTo(points[0]);
self.fBuilder.lineTo(points[0].x, -points[0].y);
}
break;
case kCGPathElementAddQuadCurveToPoint:
if (self.currentIsNot(points[0]) || self.currentIsNot(points[1])) {
self.goingTo(points[1]);
self.fBuilder.quadTo(points[0].x, -points[0].y,
points[1].x, -points[1].y);
}
break;
case kCGPathElementAddCurveToPoint:
if (self.currentIsNot(points[0]) ||
self.currentIsNot(points[1]) ||
self.currentIsNot(points[2]))
{
self.goingTo(points[2]);
self.fBuilder.cubicTo(points[0].x, -points[0].y,
points[1].x, -points[1].y,
points[2].x, -points[2].y);
}
break;
case kCGPathElementCloseSubpath:
if (self.fStarted) {
self.fBuilder.close();
}
break;
default:
SkDEBUGFAIL("Unknown path element!");
break;
}
}
};
} // namespace
/*
* Our subpixel resolution is only 2 bits in each direction, so a scale of 4
* seems sufficient, and possibly even correct, to allow the hinted outline
* to be subpixel positioned.
*/
#define kScaleForSubPixelPositionHinting (4.0f)
bool SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
SkScalar scaleX = SK_Scalar1;
SkScalar scaleY = SK_Scalar1;
CGAffineTransform xform = fTransform;
/*
* For subpixel positioning, we want to return an unhinted outline, so it
* can be positioned nicely at fractional offsets. However, we special-case
* if the baseline of the (horizontal) text is axis-aligned. In those cases
* we want to retain hinting in the direction orthogonal to the baseline.
* e.g. for horizontal baseline, we want to retain hinting in Y.
* The way we remove hinting is to scale the font by some value (4) in that
* direction, ask for the path, and then scale the path back down.
*/
if (fDoSubPosition) {
// start out by assuming that we want no hining in X and Y
scaleX = scaleY = kScaleForSubPixelPositionHinting;
// now see if we need to restore hinting for axis-aligned baselines
switch (this->computeAxisAlignmentForHText()) {
case SkAxisAlignment::kX:
scaleY = SK_Scalar1; // want hinting in the Y direction
break;
case SkAxisAlignment::kY:
scaleX = SK_Scalar1; // want hinting in the X direction
break;
default:
break;
}
CGAffineTransform scale(CGAffineTransformMakeScale(SkScalarToCGFloat(scaleX),
SkScalarToCGFloat(scaleY)));
xform = CGAffineTransformConcat(fTransform, scale);
}
CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
SkUniqueCFRef<CGPathRef> cgPath(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph, &xform));
path->reset();
if (!cgPath) {
return false;
}
SkCTPathGeometrySink sink;
CGPathApply(cgPath.get(), &sink, SkCTPathGeometrySink::ApplyElement);
*path = sink.detach();
if (fDoSubPosition) {
SkMatrix m;
m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY));
path->transform(m);
}
return true;
}
void SkScalerContext_Mac::generateFontMetrics(SkFontMetrics* metrics) {
if (nullptr == metrics) {
return;
}
CGRect theBounds = CTFontGetBoundingBox(fCTFont.get());
metrics->fTop = SkScalarFromCGFloat(-SkCGRectGetMaxY(theBounds));
metrics->fAscent = SkScalarFromCGFloat(-CTFontGetAscent(fCTFont.get()));
metrics->fDescent = SkScalarFromCGFloat( CTFontGetDescent(fCTFont.get()));
metrics->fBottom = SkScalarFromCGFloat(-SkCGRectGetMinY(theBounds));
metrics->fLeading = SkScalarFromCGFloat( CTFontGetLeading(fCTFont.get()));
metrics->fAvgCharWidth = SkScalarFromCGFloat( SkCGRectGetWidth(theBounds));
metrics->fXMin = SkScalarFromCGFloat( SkCGRectGetMinX(theBounds));
metrics->fXMax = SkScalarFromCGFloat( SkCGRectGetMaxX(theBounds));
metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin;
metrics->fXHeight = SkScalarFromCGFloat( CTFontGetXHeight(fCTFont.get()));
metrics->fCapHeight = SkScalarFromCGFloat( CTFontGetCapHeight(fCTFont.get()));
metrics->fUnderlineThickness = SkScalarFromCGFloat( CTFontGetUnderlineThickness(fCTFont.get()));
metrics->fUnderlinePosition = -SkScalarFromCGFloat( CTFontGetUnderlinePosition(fCTFont.get()));
metrics->fFlags = 0;
metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag;
metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag;
CFArrayRef ctAxes = ((SkTypeface_Mac*)this->getTypeface())->getVariationAxes();
if ((ctAxes && CFArrayGetCount(ctAxes) > 0) ||
((SkTypeface_Mac*)this->getTypeface())->fHasColorGlyphs)
{
// The bounds are only valid for the default outline variation.
// In particular `sbix` and `SVG ` data may draw outside these bounds.
metrics->fFlags |= SkFontMetrics::kBoundsInvalid_Flag;
}
// See https://bugs.chromium.org/p/skia/issues/detail?id=6203
// At least on 10.12.3 with memory based fonts the x-height is always 0.6666 of the ascent and
// the cap-height is always 0.8888 of the ascent. It appears that the values from the 'OS/2'
// table are read, but then overwritten if the font is not a system font. As a result, if there
// is a valid 'OS/2' table available use the values from the table if they aren't too strange.
struct OS2HeightMetrics {
SK_OT_SHORT sxHeight;
SK_OT_SHORT sCapHeight;
} heights;
size_t bytesRead = this->getTypeface()->getTableData(
SkTEndian_SwapBE32(SkOTTableOS2::TAG), offsetof(SkOTTableOS2, version.v2.sxHeight),
sizeof(heights), &heights);
if (bytesRead == sizeof(heights)) {
// 'fontSize' is correct because the entire resolved size is set by the constructor.
CGFloat fontSize = CTFontGetSize(this->fCTFont.get());
unsigned upem = CTFontGetUnitsPerEm(this->fCTFont.get());
unsigned maxSaneHeight = upem * 2;
uint16_t xHeight = SkEndian_SwapBE16(heights.sxHeight);
if (xHeight && xHeight < maxSaneHeight) {
metrics->fXHeight = SkScalarFromCGFloat(xHeight * fontSize / upem);
}
uint16_t capHeight = SkEndian_SwapBE16(heights.sCapHeight);
if (capHeight && capHeight < maxSaneHeight) {
metrics->fCapHeight = SkScalarFromCGFloat(capHeight * fontSize / upem);
}
}
}
#endif