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