Flutter Engine
The Flutter Engine
SkScalerContext_mac_ct.cpp
Go to the documentation of this file.
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
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
29#include "include/core/SkRect.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"
42#include "src/core/SkMemset.h"
52
53#include <algorithm>
54
55class SkDescriptor;
56
57
58namespace {
59static inline const constexpr bool kSkShowTextBlitCoverage = false;
60}
61
62static void sk_memset_rect32(uint32_t* ptr, uint32_t value,
63 int width, int height, size_t rowBytes) {
65 SkASSERT(width * sizeof(uint32_t) <= rowBytes);
66
67 if (width >= 32) {
68 while (height) {
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
106static unsigned CGRGBPixel_getAlpha(CGRGBPixel pixel) {
107 return pixel & 0xFF;
108}
109
110static 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
119SkScalerContext_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.
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
152static int RoundSize(int dimension) {
153 return SkNextPow2(dimension);
154}
155
156static 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
168SkScalerContext_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
177CGRGBPixel* 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
297SkScalerContext::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
366static 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 */
377static constexpr auto gLinearCoverageFromCGLCDValue = SkMakeArray<256>(sk_pow2_table);
378
379static 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
392template<bool APPLY_PREBLEND>
393static 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
404template<bool APPLY_PREBLEND>
405static 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
421template<bool APPLY_PREBLEND>
422static 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
435template<bool APPLY_PREBLEND>
436static 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
453static 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
464void 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) ||
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()) {
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;
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
548namespace {
549class SkCTPathGeometrySink {
550 SkPathBuilder fBuilder;
551 bool fStarted;
552 CGPoint fCurrent;
553
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
562 bool currentIsNot(const CGPoint pt) {
563 return fCurrent.x != pt.x || fCurrent.y != pt.y;
564 }
565
566public:
567 SkCTPathGeometrySink() : fStarted{false}, fCurrent{0,0} {}
568
569 SkPath detach() { return fBuilder.detach(); }
570
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
629bool SkScalerContext_Mac::generatePath(const SkGlyph& glyph, SkPath* path) {
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()) {
649 scaleY = SK_Scalar1; // want hinting in the Y direction
650 break;
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
682void 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;
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.
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);
750 }
751 uint16_t strikeoutPos = SkEndian_SwapBE16(os2v0->yStrikeoutPosition);
752 if (strikeoutPos && strikeoutPos < maxSaneHeight) {
753 metrics->fStrikeoutPosition = -SkScalarFromCGFloat(strikeoutPos * fontSize / upem);
755 }
756 }
757 }
758}
759
760#endif
static const SkColor bgColor
Definition: BlurTest.cpp:59
int count
Definition: FontMgrTest.cpp:50
static const int points[]
static const uint32_t bgra[kNumPixels]
uint16_t fFlags
Definition: ShapeLayer.cpp:106
#define SkDEBUGFAIL(message)
Definition: SkAssert.h:118
#define SkASSERT(cond)
Definition: SkAssert.h:116
unsigned U8CPU
Definition: SkCPUTypes.h:18
static U8CPU SkComputeLuminance(U8CPU r, U8CPU g, U8CPU b)
Definition: SkColorData.h:118
static U16CPU SkPack888ToRGB16(U8CPU r, U8CPU g, U8CPU b)
Definition: SkColorData.h:324
static SkPMColor SkPackARGB32(U8CPU a, U8CPU r, U8CPU g, U8CPU b)
Definition: SkColorPriv.h:106
#define SkColorGetR(color)
Definition: SkColor.h:65
#define SkColorGetG(color)
Definition: SkColor.h:69
uint32_t SkColor
Definition: SkColor.h:37
uint32_t SkPMColor
Definition: SkColor.h:205
#define SkColorGetB(color)
Definition: SkColor.h:73
#define SkEndian_SwapBE16(n)
Definition: SkEndian.h:135
#define SkTEndian_SwapBE32(n)
Definition: SkEndian.h:143
#define SK_RESTRICT
Definition: SkFeatures.h:42
#define SkFixedToFloat(x)
Definition: SkFixed.h:41
@ kNone
glyph outlines unchanged
static int SkNextPow2(int value)
Definition: SkMathPriv.h:272
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
#define SkScalarInvert(x)
Definition: SkScalar.h:73
#define SK_Scalar1
Definition: SkScalar.h:18
static constexpr bool SkToBool(const T &x)
Definition: SkTo.h:35
constexpr uint8_t SkToU8(S x)
Definition: SkTo.h:22
const void * data() const
Definition: SkData.h:37
size_t size() const
Definition: SkData.h:30
int top() const
Definition: SkGlyph.h:511
size_t rowBytes() const
Definition: SkGlyph.cpp:233
SkGlyphID getGlyphID() const
Definition: SkGlyph.h:429
SkMask::Format maskFormat() const
Definition: SkGlyph.h:500
int height() const
Definition: SkGlyph.h:513
SkFixed getSubYFixed() const
Definition: SkGlyph.h:432
SkFixed getSubXFixed() const
Definition: SkGlyph.h:431
int width() const
Definition: SkGlyph.h:512
int left() const
Definition: SkGlyph.h:510
bool isColor() const
Definition: SkGlyph.h:499
static constexpr int kMScaleX
horizontal scale factor
Definition: SkMatrix.h:353
static constexpr int kMTransY
vertical translation
Definition: SkMatrix.h:358
static constexpr int kMTransX
horizontal translation
Definition: SkMatrix.h:355
static constexpr int kMSkewY
vertical skew factor
Definition: SkMatrix.h:356
static constexpr int kMScaleY
vertical scale factor
Definition: SkMatrix.h:357
static constexpr int kMSkewX
horizontal skew factor
Definition: SkMatrix.h:354
SkPathBuilder & moveTo(SkPoint pt)
Definition: SkPath.h:59
T * get() const
Definition: SkRefCnt.h:303
void reset(T *ptr=nullptr)
Definition: SkRefCnt.h:310
float SkScalar
Definition: extension.cpp:12
static bool b
struct MyStruct a[10]
uint8_t value
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float lum(float r, float g, float b)
Definition: hsl.cpp:52
double y
double x
void(* memset32)(uint32_t[], uint32_t, int)
unsigned useCenter Optional< SkMatrix > matrix
Definition: SkRecords.h:258
sk_sp< const SkImage > image
Definition: SkRecords.h:269
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
dst
Definition: cp.py:12
Definition: ref_ptr.h:256
SkScalar w
int32_t height
int32_t width
const Scalar scale
SkScalar fTop
greatest extent above origin of any glyph bounding box, typically negative; deprecated with variable ...
Definition: SkFontMetrics.h:53
SkScalar fLeading
distance to add between lines, typically positive or zero
Definition: SkFontMetrics.h:57
SkScalar fAvgCharWidth
average character width, zero if unknown
Definition: SkFontMetrics.h:58
SkScalar fStrikeoutPosition
distance from baseline to bottom of stroke, typically negative
Definition: SkFontMetrics.h:67
SkScalar fStrikeoutThickness
strikeout thickness
Definition: SkFontMetrics.h:66
SkScalar fMaxCharWidth
maximum character width, zero if unknown
Definition: SkFontMetrics.h:59
SkScalar fBottom
greatest extent below origin of any glyph bounding box, typically positive; deprecated with variable ...
Definition: SkFontMetrics.h:56
uint32_t fFlags
FontMetricsFlags indicating which metrics are valid.
Definition: SkFontMetrics.h:52
SkScalar fAscent
distance to reserve above baseline, typically negative
Definition: SkFontMetrics.h:54
SkScalar fXHeight
height of lower-case 'x', zero if unknown, typically negative
Definition: SkFontMetrics.h:62
SkScalar fUnderlineThickness
underline thickness
Definition: SkFontMetrics.h:64
@ kStrikeoutPositionIsValid_Flag
set if fStrikeoutPosition is valid
Definition: SkFontMetrics.h:48
@ kStrikeoutThicknessIsValid_Flag
set if fStrikeoutThickness is valid
Definition: SkFontMetrics.h:47
@ kUnderlinePositionIsValid_Flag
set if fUnderlinePosition is valid
Definition: SkFontMetrics.h:46
@ kUnderlineThicknessIsValid_Flag
set if fUnderlineThickness is valid
Definition: SkFontMetrics.h:45
@ kBoundsInvalid_Flag
set if fTop, fBottom, fXMin, fXMax invalid
Definition: SkFontMetrics.h:49
SkScalar fDescent
distance to reserve below baseline, typically positive
Definition: SkFontMetrics.h:55
SkScalar fCapHeight
height of an upper-case letter, zero if unknown, typically negative
Definition: SkFontMetrics.h:63
SkScalar fXMin
greatest extent to left of origin of any glyph bounding box, typically negative; deprecated with vari...
Definition: SkFontMetrics.h:60
SkScalar fUnderlinePosition
distance from baseline to top of stroke, typically positive
Definition: SkFontMetrics.h:65
SkScalar fXMax
greatest extent to right of origin of any glyph bounding box, typically positive; deprecated with var...
Definition: SkFontMetrics.h:61
@ kA8_Format
8bits per pixel mask (e.g. antialiasing)
Definition: SkMask.h:28
@ kLCD16_Format
565 alpha for r/g/b
Definition: SkMask.h:31
@ kARGB32_Format
SkPMColor.
Definition: SkMask.h:30
@ kBW_Format
1bit per pixel mask (e.g. monochrome)
Definition: SkMask.h:27
SK_OT_SHORT yStrikeoutSize
SK_OT_SHORT yStrikeoutPosition
SK_OT_SHORT sxHeight
SK_OT_SHORT sCapHeight
static constexpr SK_OT_ULONG TAG
SkScalar fBottom
larger y-axis bounds
Definition: extension.cpp:17
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition: SkRect.h:659
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
void roundOut(SkIRect *dst) const
Definition: SkRect.h:1241
static sk_sp< SkShader > linear(sk_sp< SkShader > shader)