xref: /aosp_15_r20/external/skia/src/ports/SkScalerContext_mac_ct.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2006 The Android Open Source Project
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "include/core/SkTypes.h"
9 #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS)
10 
11 #ifdef SK_BUILD_FOR_MAC
12 #import <ApplicationServices/ApplicationServices.h>
13 #endif
14 
15 #ifdef SK_BUILD_FOR_IOS
16 #include <CoreText/CoreText.h>
17 #include <CoreText/CTFontManager.h>
18 #include <CoreGraphics/CoreGraphics.h>
19 #include <CoreFoundation/CoreFoundation.h>
20 #endif
21 
22 #include "include/core/SkColor.h"
23 #include "include/core/SkColorPriv.h"
24 #include "include/core/SkFontMetrics.h"
25 #include "include/core/SkFontTypes.h"
26 #include "include/core/SkMatrix.h"
27 #include "include/core/SkPathBuilder.h"
28 #include "include/core/SkPoint.h"
29 #include "include/core/SkRect.h"
30 #include "include/core/SkScalar.h"
31 #include "include/core/SkTypeface.h"
32 #include "include/private/SkColorData.h"
33 #include "include/private/base/SkFixed.h"
34 #include "include/private/base/SkTemplates.h"
35 #include "include/private/base/SkTo.h"
36 #include "src/base/SkAutoMalloc.h"
37 #include "src/base/SkEndian.h"
38 #include "src/base/SkMathPriv.h"
39 #include "src/core/SkGlyph.h"
40 #include "src/core/SkMask.h"
41 #include "src/core/SkMaskGamma.h"
42 #include "src/core/SkMemset.h"
43 #include "src/ports/SkScalerContext_mac_ct.h"
44 #include "src/ports/SkTypeface_mac_ct.h"
45 #include "src/sfnt/SkOTTableTypes.h"
46 #include "src/sfnt/SkOTTable_OS_2.h"
47 #include "src/utils/mac/SkCGBase.h"
48 #include "src/utils/mac/SkCGGeometry.h"
49 #include "src/utils/mac/SkCTFont.h"
50 #include "src/utils/mac/SkCTFontCreateExactCopy.h"
51 #include "src/utils/mac/SkUniqueCFRef.h"
52 
53 #include <algorithm>
54 
55 class SkDescriptor;
56 
57 
58 namespace {
59 static inline const constexpr bool kSkShowTextBlitCoverage = false;
60 }
61 
sk_memset_rect32(uint32_t * ptr,uint32_t value,int width,int height,size_t rowBytes)62 static void sk_memset_rect32(uint32_t* ptr, uint32_t value,
63                              int width, int height, size_t rowBytes) {
64     SkASSERT(width);
65     SkASSERT(width * sizeof(uint32_t) <= rowBytes);
66 
67     if (width >= 32) {
68         while (height) {
69             SkOpts::memset32(ptr, value, width);
70             ptr = (uint32_t*)((char*)ptr + rowBytes);
71             height -= 1;
72         }
73         return;
74     }
75 
76     rowBytes -= width * sizeof(uint32_t);
77 
78     if (width >= 8) {
79         while (height) {
80             int w = width;
81             do {
82                 *ptr++ = value; *ptr++ = value;
83                 *ptr++ = value; *ptr++ = value;
84                 *ptr++ = value; *ptr++ = value;
85                 *ptr++ = value; *ptr++ = value;
86                 w -= 8;
87             } while (w >= 8);
88             while (--w >= 0) {
89                 *ptr++ = value;
90             }
91             ptr = (uint32_t*)((char*)ptr + rowBytes);
92             height -= 1;
93         }
94     } else {
95         while (height) {
96             int w = width;
97             do {
98                 *ptr++ = value;
99             } while (--w > 0);
100             ptr = (uint32_t*)((char*)ptr + rowBytes);
101             height -= 1;
102         }
103     }
104 }
105 
CGRGBPixel_getAlpha(CGRGBPixel pixel)106 static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) {
107     return pixel & 0xFF;
108 }
109 
MatrixToCGAffineTransform(const SkMatrix & matrix)110 static CGAffineTransform MatrixToCGAffineTransform(const SkMatrix& matrix) {
111     return CGAffineTransformMake( SkScalarToCGFloat(matrix[SkMatrix::kMScaleX]),
112                                  -SkScalarToCGFloat(matrix[SkMatrix::kMSkewY] ),
113                                  -SkScalarToCGFloat(matrix[SkMatrix::kMSkewX] ),
114                                   SkScalarToCGFloat(matrix[SkMatrix::kMScaleY]),
115                                   SkScalarToCGFloat(matrix[SkMatrix::kMTransX]),
116                                   SkScalarToCGFloat(matrix[SkMatrix::kMTransY]));
117 }
118 
SkScalerContext_Mac(sk_sp<SkTypeface_Mac> typeface,const SkScalerContextEffects & effects,const SkDescriptor * desc)119 SkScalerContext_Mac::SkScalerContext_Mac(sk_sp<SkTypeface_Mac> typeface,
120                                          const SkScalerContextEffects& effects,
121                                          const SkDescriptor* desc)
122         : INHERITED(std::move(typeface), effects, desc)
123         , fOffscreen(fRec.fForegroundColor)
124         , fDoSubPosition(SkToBool(fRec.fFlags & kSubpixelPositioning_Flag))
125 
126 {
127     CTFontRef ctFont = (CTFontRef)this->getTypeface()->internal_private_getCTFontRef();
128 
129     // CT on (at least) 10.9 will size color glyphs down from the requested size, but not up.
130     // As a result, it is necessary to know the actual device size and request that.
131     SkVector scale;
132     SkMatrix skTransform;
133     bool invertible = fRec.computeMatrices(SkScalerContextRec::PreMatrixScale::kVertical,
134                                            &scale, &skTransform, nullptr, nullptr, nullptr);
135     fTransform = MatrixToCGAffineTransform(skTransform);
136     // CGAffineTransformInvert documents that if the transform is non-invertible it will return the
137     // passed transform unchanged. It does so, but then also prints a message to stdout. Avoid this.
138     if (invertible) {
139         fInvTransform = CGAffineTransformInvert(fTransform);
140     } else {
141         fInvTransform = fTransform;
142     }
143 
144     // The transform contains everything except the requested text size.
145     // Some properties, like 'trak', are based on the optical text size.
146     CGFloat textSize = SkScalarToCGFloat(scale.y());
147     fCTFont = SkCTFontCreateExactCopy(ctFont, textSize,
148                                       ((SkTypeface_Mac*)this->getTypeface())->fOpszVariation);
149     fCGFont.reset(CTFontCopyGraphicsFont(fCTFont.get(), nullptr));
150 }
151 
RoundSize(int dimension)152 static int RoundSize(int dimension) {
153     return SkNextPow2(dimension);
154 }
155 
CGColorForSkColor(CGColorSpaceRef rgbcs,SkColor bgra)156 static CGColorRef CGColorForSkColor(CGColorSpaceRef rgbcs, SkColor bgra) {
157     CGFloat components[4];
158     components[0] = (CGFloat)SkColorGetR(bgra) * (1/255.0f);
159     components[1] = (CGFloat)SkColorGetG(bgra) * (1/255.0f);
160     components[2] = (CGFloat)SkColorGetB(bgra) * (1/255.0f);
161     // CoreText applies the CGContext fill color as the COLR foreground color.
162     // However, the alpha is applied to the whole glyph drawing (and Skia will do that as well).
163     // For now, cannot really support COLR foreground color alpha.
164     components[3] = 1.0f;
165     return CGColorCreate(rgbcs, components);
166 }
167 
Offscreen(SkColor foregroundColor)168 SkScalerContext_Mac::Offscreen::Offscreen(SkColor foregroundColor)
169     : fCG(nullptr)
170     , fSKForegroundColor(foregroundColor)
171     , fDoAA(false)
172     , fDoLCD(false)
173 {
174     fSize.set(0, 0);
175 }
176 
getCG(const SkScalerContext_Mac & context,const SkGlyph & glyph,CGGlyph glyphID,size_t * rowBytesPtr,bool generateA8FromLCD)177 CGRGBPixel* SkScalerContext_Mac::Offscreen::getCG(const SkScalerContext_Mac& context,
178                                                   const SkGlyph& glyph, CGGlyph glyphID,
179                                                   size_t* rowBytesPtr,
180                                                   bool generateA8FromLCD) {
181     if (!fRGBSpace) {
182         //It doesn't appear to matter what color space is specified.
183         //Regular blends and antialiased text are always (s*a + d*(1-a))
184         //and subpixel antialiased text is always g=2.0.
185         fRGBSpace.reset(CGColorSpaceCreateDeviceRGB());
186         fCGForegroundColor.reset(CGColorForSkColor(fRGBSpace.get(), fSKForegroundColor));
187     }
188 
189     // default to kBW_Format
190     bool doAA = false;
191     bool doLCD = false;
192 
193     if (SkMask::kBW_Format != glyph.maskFormat()) {
194         doLCD = true;
195         doAA = true;
196     }
197 
198     // FIXME: lcd smoothed un-hinted rasterization unsupported.
199     if (!generateA8FromLCD && SkMask::kA8_Format == glyph.maskFormat()) {
200         doLCD = false;
201         doAA = true;
202     }
203 
204     // If this font might have color glyphs, disable LCD as there's no way to support it.
205     // CoreText doesn't tell us which format it ended up using, so we can't detect it.
206     // A8 will end up black on transparent, but TODO: we can detect gray and set to A8.
207     if (SkMask::kARGB32_Format == glyph.maskFormat()) {
208         doLCD = false;
209     }
210 
211     size_t rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
212     if (!fCG || fSize.fWidth < glyph.width() || fSize.fHeight < glyph.height()) {
213         if (fSize.fWidth < glyph.width()) {
214             fSize.fWidth = RoundSize(glyph.width());
215         }
216         if (fSize.fHeight < glyph.height()) {
217             fSize.fHeight = RoundSize(glyph.height());
218         }
219 
220         rowBytes = fSize.fWidth * sizeof(CGRGBPixel);
221         void* image = fImageStorage.reset(rowBytes * fSize.fHeight);
222         const CGImageAlphaInfo alpha = (glyph.isColor())
223                                      ? kCGImageAlphaPremultipliedFirst
224                                      : kCGImageAlphaNoneSkipFirst;
225         const CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host | (CGBitmapInfo)alpha;
226         fCG.reset(CGBitmapContextCreate(image, fSize.fWidth, fSize.fHeight, 8,
227                                         rowBytes, fRGBSpace.get(), bitmapInfo));
228 
229         // Skia handles quantization and subpixel positioning,
230         // so disable quantization and enable subpixel positioning in CG.
231         CGContextSetAllowsFontSubpixelQuantization(fCG.get(), false);
232         CGContextSetShouldSubpixelQuantizeFonts(fCG.get(), false);
233 
234         // Because CG always draws from the horizontal baseline,
235         // if there is a non-integral translation from the horizontal origin to the vertical origin,
236         // then CG cannot draw the glyph in the correct location without subpixel positioning.
237         CGContextSetAllowsFontSubpixelPositioning(fCG.get(), true);
238         CGContextSetShouldSubpixelPositionFonts(fCG.get(), true);
239 
240         CGContextSetTextDrawingMode(fCG.get(), kCGTextFill);
241 
242         if (SkMask::kARGB32_Format != glyph.maskFormat()) {
243             // Draw black on white to create mask. (Special path exists to speed this up in CG.)
244             CGContextSetGrayFillColor(fCG.get(), 0.0f, 1.0f);
245         } else {
246             CGContextSetFillColorWithColor(fCG.get(), fCGForegroundColor.get());
247         }
248 
249         // force our checks below to happen
250         fDoAA = !doAA;
251         fDoLCD = !doLCD;
252 
253         CGContextSetTextMatrix(fCG.get(), context.fTransform);
254     }
255 
256     if (fDoAA != doAA) {
257         CGContextSetShouldAntialias(fCG.get(), doAA);
258         fDoAA = doAA;
259     }
260     if (fDoLCD != doLCD) {
261         CGContextSetShouldSmoothFonts(fCG.get(), doLCD);
262         fDoLCD = doLCD;
263     }
264 
265     CGRGBPixel* image = (CGRGBPixel*)fImageStorage.get();
266     // skip rows based on the glyph's height
267     image += (fSize.fHeight - glyph.height()) * fSize.fWidth;
268 
269     // Erase to white (or transparent black if it's a color glyph, to not composite against white).
270     uint32_t bgColor = (!glyph.isColor()) ? 0xFFFFFFFF : 0x00000000;
271     sk_memset_rect32(image, bgColor, glyph.width(), glyph.height(), rowBytes);
272 
273     float subX = 0;
274     float subY = 0;
275     if (context.fDoSubPosition) {
276         subX = SkFixedToFloat(glyph.getSubXFixed());
277         subY = SkFixedToFloat(glyph.getSubYFixed());
278     }
279 
280     CGPoint point = CGPointMake(-glyph.left() + subX, glyph.top() + glyph.height() - subY);
281     // Prior to 10.10, CTFontDrawGlyphs acted like CGContextShowGlyphsAtPositions and took
282     // 'positions' which are in text space. The glyph location (in device space) must be
283     // mapped into text space, so that CG can convert it back into device space.
284     // In 10.10.1, this is handled directly in CTFontDrawGlyphs.
285     //
286     // However, in 10.10.2 color glyphs no longer rotate based on the font transform.
287     // So always make the font transform identity and place the transform on the context.
288     point = CGPointApplyAffineTransform(point, context.fInvTransform);
289 
290     CTFontDrawGlyphs(context.fCTFont.get(), &glyphID, &point, 1, fCG.get());
291 
292     SkASSERT(rowBytesPtr);
293     *rowBytesPtr = rowBytes;
294     return image;
295 }
296 
generateMetrics(const SkGlyph & glyph,SkArenaAlloc *)297 SkScalerContext::GlyphMetrics SkScalerContext_Mac::generateMetrics(const SkGlyph& glyph,
298                                                                    SkArenaAlloc*) {
299     GlyphMetrics mx(glyph.maskFormat());
300 
301     mx.neverRequestPath = ((SkTypeface_Mac*)this->getTypeface())->fHasColorGlyphs;
302 
303     const CGGlyph cgGlyph = (CGGlyph)glyph.getGlyphID();
304 
305     // The following block produces cgAdvance in CG units (pixels, y up).
306     CGSize cgAdvance;
307     CTFontGetAdvancesForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
308                                &cgGlyph, &cgAdvance, 1);
309     cgAdvance = CGSizeApplyAffineTransform(cgAdvance, fTransform);
310     mx.advance.fX =  SkFloatFromCGFloat(cgAdvance.width);
311     mx.advance.fY = -SkFloatFromCGFloat(cgAdvance.height);
312 
313     // The following produces skBounds in SkGlyph units (pixels, y down),
314     // or returns early if skBounds would be empty.
315     SkRect skBounds;
316 
317     // Glyphs are always drawn from the horizontal origin. The caller must manually use the result
318     // of CTFontGetVerticalTranslationsForGlyphs to calculate where to draw the glyph for vertical
319     // glyphs. As a result, always get the horizontal bounds of a glyph and translate it if the
320     // glyph is vertical. This avoids any diagreement between the various means of retrieving
321     // vertical metrics.
322     {
323         // CTFontGetBoundingRectsForGlyphs produces cgBounds in CG units (pixels, y up).
324         CGRect cgBounds;
325         CTFontGetBoundingRectsForGlyphs(fCTFont.get(), kCTFontOrientationHorizontal,
326                                         &cgGlyph, &cgBounds, 1);
327         cgBounds = CGRectApplyAffineTransform(cgBounds, fTransform);
328 
329         // BUG?
330         // 0x200B (zero-advance space) seems to return a huge (garbage) bounds, when
331         // it should be empty. So, if we see a zero-advance, we check if it has an
332         // empty path or not, and if so, we jam the bounds to 0. Hopefully a zero-advance
333         // is rare, so we won't incur a big performance cost for this extra check.
334         if (0 == cgAdvance.width && 0 == cgAdvance.height) {
335             SkUniqueCFRef<CGPathRef> path(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph,nullptr));
336             if (!path || CGPathIsEmpty(path.get())) {
337                 return mx;
338             }
339         }
340 
341         if (SkCGRectIsEmpty(cgBounds)) {
342             return mx;
343         }
344 
345         // Convert cgBounds to SkGlyph units (pixels, y down).
346         skBounds = SkRect::MakeXYWH(cgBounds.origin.x, -cgBounds.origin.y - cgBounds.size.height,
347                                     cgBounds.size.width, cgBounds.size.height);
348     }
349 
350     // Currently the bounds are based on being rendered at (0,0).
351     // The top left must not move, since that is the base from which subpixel positioning is offset.
352     if (fDoSubPosition) {
353         skBounds.fRight += SkFixedToFloat(glyph.getSubXFixed());
354         skBounds.fBottom += SkFixedToFloat(glyph.getSubYFixed());
355     }
356 
357     skBounds.roundOut(&mx.bounds);
358     // Expand the bounds by 1 pixel, to give CG room for anti-aliasing.
359     // Note that this outset is to allow room for LCD smoothed glyphs. However, the correct outset
360     // is not currently known, as CG dilates the outlines by some percentage.
361     // Note that if this context is A8 and not back-forming from LCD, there is no need to outset.
362     mx.bounds.outset(1, 1);
363     return mx;
364 }
365 
sk_pow2_table(size_t i)366 static constexpr uint8_t sk_pow2_table(size_t i) {
367     return SkToU8(((i * i + 128) / 255));
368 }
369 
370 /**
371  *  This will invert the gamma applied by CoreGraphics, so we can get linear
372  *  values.
373  *
374  *  CoreGraphics obscurely defaults to 2.0 as the subpixel coverage gamma value.
375  *  The color space used does not appear to affect this choice.
376  */
377 static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table);
378 
cgpixels_to_bits(uint8_t dst[],const CGRGBPixel src[],int count)379 static void cgpixels_to_bits(uint8_t dst[], const CGRGBPixel src[], int count) {
380     while (count > 0) {
381         uint8_t mask = 0;
382         for (int i = 7; i >= 0; --i) {
383             mask |= ((CGRGBPixel_getAlpha(*src++) >> 7) ^ 0x1) << i;
384             if (0 == --count) {
385                 break;
386             }
387         }
388         *dst++ = mask;
389     }
390 }
391 
392 template<bool APPLY_PREBLEND>
rgb_to_a8(CGRGBPixel rgb,const uint8_t * table8)393 static inline uint8_t rgb_to_a8(CGRGBPixel rgb, const uint8_t* table8) {
394     U8CPU r = 0xFF - ((rgb >> 16) & 0xFF);
395     U8CPU g = 0xFF - ((rgb >>  8) & 0xFF);
396     U8CPU b = 0xFF - ((rgb >>  0) & 0xFF);
397     U8CPU lum = sk_apply_lut_if<APPLY_PREBLEND>(SkComputeLuminance(r, g, b), table8);
398     if constexpr (kSkShowTextBlitCoverage) {
399         lum = std::max(lum, (U8CPU)0x30);
400     }
401     return lum;
402 }
403 
404 template<bool APPLY_PREBLEND>
RGBToA8(const CGRGBPixel * SK_RESTRICT cgPixels,size_t cgRowBytes,const SkGlyph & glyph,void * glyphImage,const uint8_t * table8)405 static void RGBToA8(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
406                     const SkGlyph& glyph, void* glyphImage, const uint8_t* table8) {
407     const int width = glyph.width();
408     const int height = glyph.height();
409     size_t dstRB = glyph.rowBytes();
410     uint8_t* SK_RESTRICT dst = (uint8_t*)glyphImage;
411 
412     for (int y = 0; y < height; y++) {
413         for (int i = 0; i < width; ++i) {
414             dst[i] = rgb_to_a8<APPLY_PREBLEND>(cgPixels[i], table8);
415         }
416         cgPixels = SkTAddOffset<const CGRGBPixel>(cgPixels, cgRowBytes);
417         dst = SkTAddOffset<uint8_t>(dst, dstRB);
418     }
419 }
420 
421 template<bool APPLY_PREBLEND>
RGBToLcd16(CGRGBPixel rgb,const uint8_t * tableR,const uint8_t * tableG,const uint8_t * tableB)422 static uint16_t RGBToLcd16(CGRGBPixel rgb,
423                            const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
424     U8CPU r = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >> 16) & 0xFF), tableR);
425     U8CPU g = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >>  8) & 0xFF), tableG);
426     U8CPU b = sk_apply_lut_if<APPLY_PREBLEND>(0xFF - ((rgb >>  0) & 0xFF), tableB);
427     if constexpr (kSkShowTextBlitCoverage) {
428         r = std::max(r, (U8CPU)0x30);
429         g = std::max(g, (U8CPU)0x30);
430         b = std::max(b, (U8CPU)0x30);
431     }
432     return SkPack888ToRGB16(r, g, b);
433 }
434 
435 template<bool APPLY_PREBLEND>
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)436 static void RGBToLcd16(const CGRGBPixel* SK_RESTRICT cgPixels, size_t cgRowBytes,
437                        const SkGlyph& glyph, void* glyphImage,
438                        const uint8_t* tableR, const uint8_t* tableG, const uint8_t* tableB) {
439     const int width = glyph.width();
440     const int height = glyph.height();
441     size_t dstRB = glyph.rowBytes();
442     uint16_t* SK_RESTRICT dst = (uint16_t*)glyphImage;
443 
444     for (int y = 0; y < height; y++) {
445         for (int i = 0; i < width; i++) {
446             dst[i] = RGBToLcd16<APPLY_PREBLEND>(cgPixels[i], tableR, tableG, tableB);
447         }
448         cgPixels = SkTAddOffset<const CGRGBPixel>(cgPixels, cgRowBytes);
449         dst = SkTAddOffset<uint16_t>(dst, dstRB);
450     }
451 }
452 
cgpixels_to_pmcolor(CGRGBPixel rgb)453 static SkPMColor cgpixels_to_pmcolor(CGRGBPixel rgb) {
454     U8CPU a = (rgb >> 24) & 0xFF;
455     U8CPU r = (rgb >> 16) & 0xFF;
456     U8CPU g = (rgb >>  8) & 0xFF;
457     U8CPU b = (rgb >>  0) & 0xFF;
458     if constexpr (kSkShowTextBlitCoverage) {
459         a = std::max(a, (U8CPU)0x30);
460     }
461     return SkPackARGB32(a, r, g, b);
462 }
463 
generateImage(const SkGlyph & glyph,void * imageBuffer)464 void SkScalerContext_Mac::generateImage(const SkGlyph& glyph, void* imageBuffer) {
465     CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
466 
467     // FIXME: lcd smoothed un-hinted rasterization unsupported.
468     bool requestSmooth = fRec.getHinting() != SkFontHinting::kNone;
469 
470     // Draw the glyph
471     size_t cgRowBytes;
472     CGRGBPixel* cgPixels = fOffscreen.getCG(*this, glyph, cgGlyph, &cgRowBytes, requestSmooth);
473     if (cgPixels == nullptr) {
474         return;
475     }
476 
477     // Fix the glyph
478     if ((glyph.maskFormat() == SkMask::kLCD16_Format) ||
479         (glyph.maskFormat() == SkMask::kA8_Format
480          && requestSmooth
481          && SkCTFontGetSmoothBehavior() != SkCTFontSmoothBehavior::none))
482     {
483         const uint8_t* linear = gLinearCoverageFromCGLCDValue.data();
484 
485         //Note that the following cannot really be integrated into the
486         //pre-blend, since we may not be applying the pre-blend; when we aren't
487         //applying the pre-blend it means that a filter wants linear anyway.
488         //Other code may also be applying the pre-blend, so we'd need another
489         //one with this and one without.
490         CGRGBPixel* addr = cgPixels;
491         for (int y = 0; y < glyph.height(); ++y) {
492             for (int x = 0; x < glyph.width(); ++x) {
493                 int r = (addr[x] >> 16) & 0xFF;
494                 int g = (addr[x] >>  8) & 0xFF;
495                 int b = (addr[x] >>  0) & 0xFF;
496                 addr[x] = (linear[r] << 16) | (linear[g] << 8) | linear[b];
497             }
498             addr = SkTAddOffset<CGRGBPixel>(addr, cgRowBytes);
499         }
500     }
501 
502     // Convert glyph to mask
503     switch (glyph.maskFormat()) {
504         case SkMask::kLCD16_Format: {
505             if (fPreBlend.isApplicable()) {
506                 RGBToLcd16<true>(cgPixels, cgRowBytes, glyph, imageBuffer,
507                                  fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
508             } else {
509                 RGBToLcd16<false>(cgPixels, cgRowBytes, glyph, imageBuffer,
510                                   fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
511             }
512         } break;
513         case SkMask::kA8_Format: {
514             if (fPreBlend.isApplicable()) {
515                 RGBToA8<true>(cgPixels, cgRowBytes, glyph, imageBuffer, fPreBlend.fG);
516             } else {
517                 RGBToA8<false>(cgPixels, cgRowBytes, glyph, imageBuffer, fPreBlend.fG);
518             }
519         } break;
520         case SkMask::kBW_Format: {
521             const int width = glyph.width();
522             size_t dstRB = glyph.rowBytes();
523             uint8_t* dst = (uint8_t*)imageBuffer;
524             for (int y = 0; y < glyph.height(); y++) {
525                 cgpixels_to_bits(dst, cgPixels, width);
526                 cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
527                 dst = SkTAddOffset<uint8_t>(dst, dstRB);
528             }
529         } break;
530         case SkMask::kARGB32_Format: {
531             const int width = glyph.width();
532             size_t dstRB = glyph.rowBytes();
533             SkPMColor* dst = (SkPMColor*)imageBuffer;
534             for (int y = 0; y < glyph.height(); y++) {
535                 for (int x = 0; x < width; ++x) {
536                     dst[x] = cgpixels_to_pmcolor(cgPixels[x]);
537                 }
538                 cgPixels = SkTAddOffset<CGRGBPixel>(cgPixels, cgRowBytes);
539                 dst = SkTAddOffset<SkPMColor>(dst, dstRB);
540             }
541         } break;
542         default:
543             SkDEBUGFAIL("unexpected mask format");
544             break;
545     }
546 }
547 
548 namespace {
549 class SkCTPathGeometrySink {
550     SkPathBuilder fBuilder;
551     bool fStarted;
552     CGPoint fCurrent;
553 
goingTo(const CGPoint pt)554     void goingTo(const CGPoint pt) {
555         if (!fStarted) {
556             fStarted = true;
557             fBuilder.moveTo(fCurrent.x, -fCurrent.y);
558         }
559         fCurrent = pt;
560     }
561 
currentIsNot(const CGPoint pt)562     bool currentIsNot(const CGPoint pt) {
563         return fCurrent.x != pt.x || fCurrent.y != pt.y;
564     }
565 
566 public:
SkCTPathGeometrySink()567     SkCTPathGeometrySink() : fStarted{false}, fCurrent{0,0} {}
568 
detach()569     SkPath detach() { return fBuilder.detach(); }
570 
ApplyElement(void * ctx,const CGPathElement * element)571     static void ApplyElement(void *ctx, const CGPathElement *element) {
572         SkCTPathGeometrySink& self = *(SkCTPathGeometrySink*)ctx;
573         CGPoint* points = element->points;
574 
575         switch (element->type) {
576             case kCGPathElementMoveToPoint:
577                 self.fStarted = false;
578                 self.fCurrent = points[0];
579                 break;
580 
581             case kCGPathElementAddLineToPoint:
582                 if (self.currentIsNot(points[0])) {
583                     self.goingTo(points[0]);
584                     self.fBuilder.lineTo(points[0].x, -points[0].y);
585                 }
586                 break;
587 
588             case kCGPathElementAddQuadCurveToPoint:
589                 if (self.currentIsNot(points[0]) || self.currentIsNot(points[1])) {
590                     self.goingTo(points[1]);
591                     self.fBuilder.quadTo(points[0].x, -points[0].y,
592                                          points[1].x, -points[1].y);
593                 }
594                 break;
595 
596             case kCGPathElementAddCurveToPoint:
597                 if (self.currentIsNot(points[0]) ||
598                     self.currentIsNot(points[1]) ||
599                     self.currentIsNot(points[2]))
600                 {
601                     self.goingTo(points[2]);
602                     self.fBuilder.cubicTo(points[0].x, -points[0].y,
603                                           points[1].x, -points[1].y,
604                                           points[2].x, -points[2].y);
605                 }
606                 break;
607 
608             case kCGPathElementCloseSubpath:
609                 if (self.fStarted) {
610                     self.fBuilder.close();
611                 }
612                 break;
613 
614             default:
615                 SkDEBUGFAIL("Unknown path element!");
616                 break;
617             }
618     }
619 };
620 } // namespace
621 
622 /*
623  *  Our subpixel resolution is only 2 bits in each direction, so a scale of 4
624  *  seems sufficient, and possibly even correct, to allow the hinted outline
625  *  to be subpixel positioned.
626  */
627 #define kScaleForSubPixelPositionHinting (4.0f)
628 
generatePath(const SkGlyph & glyph,SkPath * path,bool * modified)629 bool SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path, bool* modified) {
630     SkScalar scaleX = SK_Scalar1;
631     SkScalar scaleY = SK_Scalar1;
632 
633     CGAffineTransform xform = fTransform;
634     /*
635      *  For subpixel positioning, we want to return an unhinted outline, so it
636      *  can be positioned nicely at fractional offsets. However, we special-case
637      *  if the baseline of the (horizontal) text is axis-aligned. In those cases
638      *  we want to retain hinting in the direction orthogonal to the baseline.
639      *  e.g. for horizontal baseline, we want to retain hinting in Y.
640      *  The way we remove hinting is to scale the font by some value (4) in that
641      *  direction, ask for the path, and then scale the path back down.
642      */
643     if (fDoSubPosition) {
644         // start out by assuming that we want no hining in X and Y
645         scaleX = scaleY = kScaleForSubPixelPositionHinting;
646         // now see if we need to restore hinting for axis-aligned baselines
647         switch (this->computeAxisAlignmentForHText()) {
648             case SkAxisAlignment::kX:
649                 scaleY = SK_Scalar1; // want hinting in the Y direction
650                 break;
651             case SkAxisAlignment::kY:
652                 scaleX = SK_Scalar1; // want hinting in the X direction
653                 break;
654             default:
655                 break;
656         }
657 
658         CGAffineTransform scale(CGAffineTransformMakeScale(SkScalarToCGFloat(scaleX),
659                                                            SkScalarToCGFloat(scaleY)));
660         xform = CGAffineTransformConcat(fTransform, scale);
661     }
662 
663     CGGlyph cgGlyph = SkTo<CGGlyph>(glyph.getGlyphID());
664     SkUniqueCFRef<CGPathRef> cgPath(CTFontCreatePathForGlyph(fCTFont.get(), cgGlyph, &xform));
665 
666     path->reset();
667     if (!cgPath) {
668         return false;
669     }
670 
671     SkCTPathGeometrySink sink;
672     CGPathApply(cgPath.get(), &sink, SkCTPathGeometrySink::ApplyElement);
673     *path = sink.detach();
674     if (fDoSubPosition) {
675         SkMatrix m;
676         m.setScale(SkScalarInvert(scaleX), SkScalarInvert(scaleY));
677         path->transform(m);
678     }
679     return true;
680 }
681 
generateFontMetrics(SkFontMetrics * metrics)682 void SkScalerContext_Mac::generateFontMetrics(SkFontMetrics* metrics) {
683     if (nullptr == metrics) {
684         return;
685     }
686 
687     CGRect theBounds = CTFontGetBoundingBox(fCTFont.get());
688 
689     metrics->fTop          = SkScalarFromCGFloat(-SkCGRectGetMaxY(theBounds));
690     metrics->fAscent       = SkScalarFromCGFloat(-CTFontGetAscent(fCTFont.get()));
691     metrics->fDescent      = SkScalarFromCGFloat( CTFontGetDescent(fCTFont.get()));
692     metrics->fBottom       = SkScalarFromCGFloat(-SkCGRectGetMinY(theBounds));
693     metrics->fLeading      = SkScalarFromCGFloat( CTFontGetLeading(fCTFont.get()));
694     metrics->fAvgCharWidth = SkScalarFromCGFloat( SkCGRectGetWidth(theBounds));
695     metrics->fXMin         = SkScalarFromCGFloat( SkCGRectGetMinX(theBounds));
696     metrics->fXMax         = SkScalarFromCGFloat( SkCGRectGetMaxX(theBounds));
697     metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin;
698     metrics->fXHeight      = SkScalarFromCGFloat( CTFontGetXHeight(fCTFont.get()));
699     metrics->fCapHeight    = SkScalarFromCGFloat( CTFontGetCapHeight(fCTFont.get()));
700     metrics->fUnderlineThickness = SkScalarFromCGFloat( CTFontGetUnderlineThickness(fCTFont.get()));
701     metrics->fUnderlinePosition = -SkScalarFromCGFloat( CTFontGetUnderlinePosition(fCTFont.get()));
702     metrics->fStrikeoutThickness = 0;
703     metrics->fStrikeoutPosition = 0;
704 
705     metrics->fFlags = 0;
706     metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag;
707     metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag;
708 
709     CFArrayRef ctAxes = ((SkTypeface_Mac*)this->getTypeface())->getVariationAxes();
710     if ((ctAxes && CFArrayGetCount(ctAxes) > 0) ||
711         ((SkTypeface_Mac*)this->getTypeface())->fHasColorGlyphs)
712     {
713         // The bounds are only valid for the default outline variation.
714         // In particular `sbix` and `SVG ` data may draw outside these bounds.
715         metrics->fFlags |= SkFontMetrics::kBoundsInvalid_Flag;
716     }
717 
718     sk_sp<SkData> os2 = this->getTypeface()->copyTableData(SkTEndian_SwapBE32(SkOTTableOS2::TAG));
719     if (os2) {
720         // 'fontSize' is correct because the entire resolved size is set by the constructor.
721         const CGFloat fontSize = CTFontGetSize(fCTFont.get());
722         const unsigned int upem = CTFontGetUnitsPerEm(fCTFont.get());
723         const unsigned int maxSaneHeight = upem * 2;
724 
725         // See https://bugs.chromium.org/p/skia/issues/detail?id=6203
726         // At least on 10.12.3 with memory based fonts the x-height is always 0.6666 of the ascent
727         // and the cap-height is always 0.8888 of the ascent. It appears that the values from the
728         // 'OS/2' table are read, but then overwritten if the font is not a system font. As a
729         // result, if there is a valid 'OS/2' table available use the values from the table if they
730         // aren't too strange.
731         if (sizeof(SkOTTableOS2_V2) <= os2->size()) {
732             const SkOTTableOS2_V2* os2v2 = static_cast<const SkOTTableOS2_V2*>(os2->data());
733             uint16_t xHeight = SkEndian_SwapBE16(os2v2->sxHeight);
734             if (xHeight && xHeight < maxSaneHeight) {
735                 metrics->fXHeight = SkScalarFromCGFloat(xHeight * fontSize / upem);
736             }
737             uint16_t capHeight = SkEndian_SwapBE16(os2v2->sCapHeight);
738             if (capHeight && capHeight < maxSaneHeight) {
739                 metrics->fCapHeight = SkScalarFromCGFloat(capHeight * fontSize / upem);
740             }
741         }
742 
743         // CoreText does not provide the strikeout metrics, which are available in OS/2 version 0.
744         if (sizeof(SkOTTableOS2_V0) <= os2->size()) {
745             const SkOTTableOS2_V0* os2v0 = static_cast<const SkOTTableOS2_V0*>(os2->data());
746             uint16_t strikeoutSize = SkEndian_SwapBE16(os2v0->yStrikeoutSize);
747             if (strikeoutSize && strikeoutSize < maxSaneHeight) {
748                 metrics->fStrikeoutThickness = SkScalarFromCGFloat(strikeoutSize * fontSize / upem);
749                 metrics->fFlags |= SkFontMetrics::kStrikeoutThicknessIsValid_Flag;
750             }
751             uint16_t strikeoutPos = SkEndian_SwapBE16(os2v0->yStrikeoutPosition);
752             if (strikeoutPos && strikeoutPos < maxSaneHeight) {
753                 metrics->fStrikeoutPosition = -SkScalarFromCGFloat(strikeoutPos * fontSize / upem);
754                 metrics->fFlags |= SkFontMetrics::kStrikeoutPositionIsValid_Flag;
755             }
756         }
757     }
758 }
759 
760 #endif
761