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