diff options
Diffstat (limited to 'gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp')
-rw-r--r-- | gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp b/gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp new file mode 100644 index 0000000000..1e61bbd775 --- /dev/null +++ b/gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp @@ -0,0 +1,789 @@ +/* + * 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/base/SkFixed.h" +#include "include/private/base/SkTemplates.h" +#include "include/private/base/SkTo.h" +#include "src/base/SkAutoMalloc.h" +#include "src/base/SkEndian.h" +#include "src/base/SkMathPriv.h" +#include "src/core/SkGlyph.h" +#include "src/core/SkMask.h" +#include "src/core/SkMaskGamma.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) { + SkOpts::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, + bool lightOnDark) { + 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.) + // If light-on-dark is requested, draw white on black. + CGContextSetGrayFillColor(fCG.get(), lightOnDark ? 1.0f : 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). + // For light-on-dark, instead erase to black. + uint32_t bgColor = (!glyph.isColor()) ? (lightOnDark ? 0xFF000000 : 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; + +#ifndef MOZ_SKIA + if (((SkTypeface_Mac*)this->getTypeface())->fHasColorGlyphs) { + glyph->setPath(alloc, nullptr, false); + } +#endif + + 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. + // Avoid trying to create a path from a color font due to crashing on 10.9. + if (0 == cgAdvance.width && 0 == cgAdvance.height && + SkMask::kARGB32_Format != glyph->fMaskFormat) { + 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; + bool lightOnDark = (fRec.fFlags & SkScalerContext::kLightOnDark_Flag) != 0; + + // Draw the glyph + size_t cgRowBytes; + CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth, lightOnDark); + 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 = linear[(addr[x] >> 16) & 0xFF]; + int g = linear[(addr[x] >> 8) & 0xFF]; + int b = linear[(addr[x] >> 0) & 0xFF]; + // If light-on-dark was requested, the mask is drawn inverted. + if (lightOnDark) { + r = 255 - r; + g = 255 - g; + b = 255 - b; + } + addr[x] = (r << 16) | (g << 8) | 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->fStrikeoutThickness = 0; + metrics->fStrikeoutPosition = 0; + + 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; + } + + sk_sp<SkData> os2 = this->getTypeface()->copyTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG)); + if (os2) { + // 'fontSize' is correct because the entire resolved size is set by the constructor. + const CGFloat fontSize = CTFontGetSize(fCTFont.get()); + const unsigned int upem = CTFontGetUnitsPerEm(fCTFont.get()); + const unsigned int maxSaneHeight = upem * 2; + + // 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. + if (sizeof(SkOTTableOS2_V2) <= os2->size()) { + const SkOTTableOS2_V2* os2v2 = static_cast<const SkOTTableOS2_V2*>(os2->data()); + uint16_t xHeight = SkEndian_SwapBE16(os2v2->sxHeight); + if (xHeight && xHeight < maxSaneHeight) { + metrics->fXHeight = SkScalarFromCGFloat(xHeight * fontSize / upem); + } + uint16_t capHeight = SkEndian_SwapBE16(os2v2->sCapHeight); + if (capHeight && capHeight < maxSaneHeight) { + metrics->fCapHeight = SkScalarFromCGFloat(capHeight * fontSize / upem); + } + } + + // CoreText does not provide the strikeout metrics, which are available in OS/2 version 0. + if (sizeof(SkOTTableOS2_V0) <= os2->size()) { + const SkOTTableOS2_V0* os2v0 = static_cast<const SkOTTableOS2_V0*>(os2->data()); + uint16_t strikeoutSize = SkEndian_SwapBE16(os2v0->yStrikeoutSize); + if (strikeoutSize && strikeoutSize < maxSaneHeight) { + metrics->fStrikeoutThickness = SkScalarFromCGFloat(strikeoutSize * fontSize / upem); + metrics->fFlags |= SkFontMetrics::kStrikeoutThicknessIsValid_Flag; + } + uint16_t strikeoutPos = SkEndian_SwapBE16(os2v0->yStrikeoutPosition); + if (strikeoutPos && strikeoutPos < maxSaneHeight) { + metrics->fStrikeoutPosition = -SkScalarFromCGFloat(strikeoutPos * fontSize / upem); + metrics->fFlags |= SkFontMetrics::kStrikeoutPositionIsValid_Flag; + } + } + } +} + +#endif |