summaryrefslogtreecommitdiffstats
path: root/gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp')
-rw-r--r--gfx/skia/skia/src/ports/SkScalerContext_mac_ct.cpp789
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