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