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/SkPaint.h"
9#include "src/core/SkScalerContext.h"
10
11#include "include/core/SkFontMetrics.h"
12#include "include/core/SkMaskFilter.h"
13#include "include/core/SkPathEffect.h"
14#include "include/core/SkStrokeRec.h"
15#include "include/private/SkColorData.h"
16#include "include/private/SkTo.h"
17#include "src/core/SkAutoMalloc.h"
18#include "src/core/SkAutoPixmapStorage.h"
19#include "src/core/SkDescriptor.h"
20#include "src/core/SkDraw.h"
21#include "src/core/SkFontPriv.h"
22#include "src/core/SkGlyph.h"
23#include "src/core/SkMaskGamma.h"
24#include "src/core/SkPaintPriv.h"
25#include "src/core/SkPathPriv.h"
26#include "src/core/SkRasterClip.h"
27#include "src/core/SkReadBuffer.h"
28#include "src/core/SkRectPriv.h"
29#include "src/core/SkStroke.h"
30#include "src/core/SkSurfacePriv.h"
31#include "src/core/SkTextFormatParams.h"
32#include "src/core/SkWriteBuffer.h"
33#include "src/utils/SkMatrix22.h"
34#include <new>
35
36///////////////////////////////////////////////////////////////////////////////
37
38#ifdef SK_DEBUG
39 #define DUMP_RECx
40#endif
41
42SkScalerContextRec SkScalerContext::PreprocessRec(const SkTypeface& typeface,
43 const SkScalerContextEffects& effects,
44 const SkDescriptor& desc) {
45 SkScalerContextRec rec =
46 *static_cast<const SkScalerContextRec*>(desc.findEntry(kRec_SkDescriptorTag, nullptr));
47
48 // Allow the typeface to adjust the rec.
49 typeface.onFilterRec(&rec);
50
51 if (effects.fMaskFilter) {
52 // Pre-blend is not currently applied to filtered text.
53 // The primary filter is blur, for which contrast makes no sense,
54 // and for which the destination guess error is more visible.
55 // Also, all existing users of blur have calibrated for linear.
56 rec.ignorePreBlend();
57 }
58
59 SkColor lumColor = rec.getLuminanceColor();
60
61 if (rec.fMaskFormat == SkMask::kA8_Format) {
62 U8CPU lum = SkComputeLuminance(SkColorGetR(lumColor),
63 SkColorGetG(lumColor),
64 SkColorGetB(lumColor));
65 lumColor = SkColorSetRGB(lum, lum, lum);
66 }
67
68 // TODO: remove CanonicalColor when we to fix up Chrome layout tests.
69 rec.setLuminanceColor(lumColor);
70
71 return rec;
72}
73
74SkScalerContext::SkScalerContext(sk_sp<SkTypeface> typeface, const SkScalerContextEffects& effects,
75 const SkDescriptor* desc)
76 : fRec(PreprocessRec(*typeface, effects, *desc))
77 , fTypeface(std::move(typeface))
78 , fPathEffect(sk_ref_sp(effects.fPathEffect))
79 , fMaskFilter(sk_ref_sp(effects.fMaskFilter))
80 // Initialize based on our settings. Subclasses can also force this.
81 , fGenerateImageFromPath(fRec.fFrameWidth > 0 || fPathEffect != nullptr)
82
83 , fPreBlend(fMaskFilter ? SkMaskGamma::PreBlend() : SkScalerContext::GetMaskPreBlend(fRec))
84{
85#ifdef DUMP_REC
86 SkDebugf("SkScalerContext checksum %x count %d length %d\n",
87 desc->getChecksum(), desc->getCount(), desc->getLength());
88 SkDebugf("%s", fRec.dump().c_str());
89 SkDebugf(" effects %x\n", desc->findEntry(kEffects_SkDescriptorTag, nullptr));
90#endif
91}
92
93SkScalerContext::~SkScalerContext() {}
94
95/**
96 * In order to call cachedDeviceLuminance, cachedPaintLuminance, or
97 * cachedMaskGamma the caller must hold the mask_gamma_cache_mutex and continue
98 * to hold it until the returned pointer is refed or forgotten.
99 */
100static SkMutex& mask_gamma_cache_mutex() {
101 static SkMutex& mutex = *(new SkMutex);
102 return mutex;
103}
104
105static SkMaskGamma* gLinearMaskGamma = nullptr;
106static SkMaskGamma* gMaskGamma = nullptr;
107static SkScalar gContrast = SK_ScalarMin;
108static SkScalar gPaintGamma = SK_ScalarMin;
109static SkScalar gDeviceGamma = SK_ScalarMin;
110
111/**
112 * The caller must hold the mask_gamma_cache_mutex() and continue to hold it until
113 * the returned SkMaskGamma pointer is refed or forgotten.
114 */
115static const SkMaskGamma& cached_mask_gamma(SkScalar contrast, SkScalar paintGamma,
116 SkScalar deviceGamma) {
117 mask_gamma_cache_mutex().assertHeld();
118 if (0 == contrast && SK_Scalar1 == paintGamma && SK_Scalar1 == deviceGamma) {
119 if (nullptr == gLinearMaskGamma) {
120 gLinearMaskGamma = new SkMaskGamma;
121 }
122 return *gLinearMaskGamma;
123 }
124 if (gContrast != contrast || gPaintGamma != paintGamma || gDeviceGamma != deviceGamma) {
125 SkSafeUnref(gMaskGamma);
126 gMaskGamma = new SkMaskGamma(contrast, paintGamma, deviceGamma);
127 gContrast = contrast;
128 gPaintGamma = paintGamma;
129 gDeviceGamma = deviceGamma;
130 }
131 return *gMaskGamma;
132}
133
134/**
135 * Expands fDeviceGamma, fPaintGamma, fContrast, and fLumBits into a mask pre-blend.
136 */
137SkMaskGamma::PreBlend SkScalerContext::GetMaskPreBlend(const SkScalerContextRec& rec) {
138 SkAutoMutexExclusive ama(mask_gamma_cache_mutex());
139
140 const SkMaskGamma& maskGamma = cached_mask_gamma(rec.getContrast(),
141 rec.getPaintGamma(),
142 rec.getDeviceGamma());
143
144 // TODO: remove CanonicalColor when we to fix up Chrome layout tests.
145 return maskGamma.preBlend(rec.getLuminanceColor());
146}
147
148size_t SkScalerContext::GetGammaLUTSize(SkScalar contrast, SkScalar paintGamma,
149 SkScalar deviceGamma, int* width, int* height) {
150 SkAutoMutexExclusive ama(mask_gamma_cache_mutex());
151 const SkMaskGamma& maskGamma = cached_mask_gamma(contrast,
152 paintGamma,
153 deviceGamma);
154
155 maskGamma.getGammaTableDimensions(width, height);
156 size_t size = (*width)*(*height)*sizeof(uint8_t);
157
158 return size;
159}
160
161bool SkScalerContext::GetGammaLUTData(SkScalar contrast, SkScalar paintGamma, SkScalar deviceGamma,
162 uint8_t* data) {
163 SkAutoMutexExclusive ama(mask_gamma_cache_mutex());
164 const SkMaskGamma& maskGamma = cached_mask_gamma(contrast,
165 paintGamma,
166 deviceGamma);
167 const uint8_t* gammaTables = maskGamma.getGammaTables();
168 if (!gammaTables) {
169 return false;
170 }
171
172 int width, height;
173 maskGamma.getGammaTableDimensions(&width, &height);
174 size_t size = width*height * sizeof(uint8_t);
175 memcpy(data, gammaTables, size);
176 return true;
177}
178
179void SkScalerContext::getAdvance(SkGlyph* glyph) {
180 if (generateAdvance(glyph)) {
181 glyph->fMaskFormat = MASK_FORMAT_JUST_ADVANCE;
182 } else {
183 this->getMetrics(glyph);
184 SkASSERT(glyph->fMaskFormat != MASK_FORMAT_UNKNOWN);
185 }
186}
187
188void SkScalerContext::getMetrics(SkGlyph* glyph) {
189 bool generatingImageFromPath = fGenerateImageFromPath;
190 if (!generatingImageFromPath) {
191 generateMetrics(glyph);
192 SkASSERT(glyph->fMaskFormat != MASK_FORMAT_UNKNOWN);
193 } else {
194 SkPath devPath;
195 generatingImageFromPath = this->internalGetPath(glyph->getPackedID(), &devPath);
196 if (!generatingImageFromPath) {
197 generateMetrics(glyph);
198 SkASSERT(glyph->fMaskFormat != MASK_FORMAT_UNKNOWN);
199 } else {
200 uint8_t originMaskFormat = glyph->fMaskFormat;
201 if (!generateAdvance(glyph)) {
202 generateMetrics(glyph);
203 }
204
205 if (originMaskFormat != MASK_FORMAT_UNKNOWN) {
206 glyph->fMaskFormat = originMaskFormat;
207 } else {
208 glyph->fMaskFormat = fRec.fMaskFormat;
209 }
210
211 // If we are going to create the mask, then we cannot keep the color
212 if (SkMask::kARGB32_Format == glyph->fMaskFormat) {
213 glyph->fMaskFormat = SkMask::kA8_Format;
214 }
215
216 const SkIRect ir = devPath.getBounds().roundOut();
217 if (ir.isEmpty() || !SkRectPriv::Is16Bit(ir)) {
218 goto SK_ERROR;
219 }
220 glyph->fLeft = ir.fLeft;
221 glyph->fTop = ir.fTop;
222 glyph->fWidth = SkToU16(ir.width());
223 glyph->fHeight = SkToU16(ir.height());
224
225 if (glyph->fWidth > 0) {
226 switch (glyph->fMaskFormat) {
227 case SkMask::kLCD16_Format:
228 if (fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag) {
229 glyph->fHeight += 2;
230 glyph->fTop -= 1;
231 } else {
232 glyph->fWidth += 2;
233 glyph->fLeft -= 1;
234 }
235 break;
236 default:
237 break;
238 }
239 }
240 }
241 }
242
243 // if either dimension is empty, zap the image bounds of the glyph
244 if (0 == glyph->fWidth || 0 == glyph->fHeight) {
245 glyph->fWidth = 0;
246 glyph->fHeight = 0;
247 glyph->fTop = 0;
248 glyph->fLeft = 0;
249 glyph->fMaskFormat = 0;
250 return;
251 }
252
253 if (fMaskFilter) {
254 SkMask src = glyph->mask(),
255 dst;
256 SkMatrix matrix;
257
258 fRec.getMatrixFrom2x2(&matrix);
259
260 src.fImage = nullptr; // only want the bounds from the filter
261 if (as_MFB(fMaskFilter)->filterMask(&dst, src, matrix, nullptr)) {
262 if (dst.fBounds.isEmpty() || !SkRectPriv::Is16Bit(dst.fBounds)) {
263 goto SK_ERROR;
264 }
265 SkASSERT(dst.fImage == nullptr);
266 glyph->fLeft = dst.fBounds.fLeft;
267 glyph->fTop = dst.fBounds.fTop;
268 glyph->fWidth = SkToU16(dst.fBounds.width());
269 glyph->fHeight = SkToU16(dst.fBounds.height());
270 glyph->fMaskFormat = dst.fFormat;
271 }
272 }
273 return;
274
275SK_ERROR:
276 // draw nothing 'cause we failed
277 glyph->fLeft = 0;
278 glyph->fTop = 0;
279 glyph->fWidth = 0;
280 glyph->fHeight = 0;
281 // put a valid value here, in case it was earlier set to
282 // MASK_FORMAT_JUST_ADVANCE
283 glyph->fMaskFormat = fRec.fMaskFormat;
284}
285
286#define SK_SHOW_TEXT_BLIT_COVERAGE 0
287
288static void applyLUTToA8Mask(const SkMask& mask, const uint8_t* lut) {
289 uint8_t* SK_RESTRICT dst = (uint8_t*)mask.fImage;
290 unsigned rowBytes = mask.fRowBytes;
291
292 for (int y = mask.fBounds.height() - 1; y >= 0; --y) {
293 for (int x = mask.fBounds.width() - 1; x >= 0; --x) {
294 dst[x] = lut[dst[x]];
295 }
296 dst += rowBytes;
297 }
298}
299
300static void pack4xHToLCD16(const SkPixmap& src, const SkMask& dst,
301 const SkMaskGamma::PreBlend& maskPreBlend,
302 const bool doBGR, const bool doVert) {
303#define SAMPLES_PER_PIXEL 4
304#define LCD_PER_PIXEL 3
305 SkASSERT(kAlpha_8_SkColorType == src.colorType());
306 SkASSERT(SkMask::kLCD16_Format == dst.fFormat);
307
308 // doVert in this function means swap x and y when writing to dst.
309 if (doVert) {
310 SkASSERT(src.width() == (dst.fBounds.height() - 2) * 4);
311 SkASSERT(src.height() == dst.fBounds.width());
312 } else {
313 SkASSERT(src.width() == (dst.fBounds.width() - 2) * 4);
314 SkASSERT(src.height() == dst.fBounds.height());
315 }
316
317 const int sample_width = src.width();
318 const int height = src.height();
319
320 uint16_t* dstImage = (uint16_t*)dst.fImage;
321 size_t dstRB = dst.fRowBytes;
322 // An N tap FIR is defined by
323 // out[n] = coeff[0]*x[n] + coeff[1]*x[n-1] + ... + coeff[N]*x[n-N]
324 // or
325 // out[n] = sum(i, 0, N, coeff[i]*x[n-i])
326
327 // The strategy is to use one FIR (different coefficients) for each of r, g, and b.
328 // This means using every 4th FIR output value of each FIR and discarding the rest.
329 // The FIRs are aligned, and the coefficients reach 5 samples to each side of their 'center'.
330 // (For r and b this is technically incorrect, but the coeffs outside round to zero anyway.)
331
332 // These are in some fixed point repesentation.
333 // Adding up to more than one simulates ink spread.
334 // For implementation reasons, these should never add up to more than two.
335
336 // Coefficients determined by a gausian where 5 samples = 3 std deviations (0x110 'contrast').
337 // Calculated using tools/generate_fir_coeff.py
338 // With this one almost no fringing is ever seen, but it is imperceptibly blurry.
339 // The lcd smoothed text is almost imperceptibly different from gray,
340 // but is still sharper on small stems and small rounded corners than gray.
341 // This also seems to be about as wide as one can get and only have a three pixel kernel.
342 // TODO: calculate these at runtime so parameters can be adjusted (esp contrast).
343 static const unsigned int coefficients[LCD_PER_PIXEL][SAMPLES_PER_PIXEL*3] = {
344 //The red subpixel is centered inside the first sample (at 1/6 pixel), and is shifted.
345 { 0x03, 0x0b, 0x1c, 0x33, 0x40, 0x39, 0x24, 0x10, 0x05, 0x01, 0x00, 0x00, },
346 //The green subpixel is centered between two samples (at 1/2 pixel), so is symetric
347 { 0x00, 0x02, 0x08, 0x16, 0x2b, 0x3d, 0x3d, 0x2b, 0x16, 0x08, 0x02, 0x00, },
348 //The blue subpixel is centered inside the last sample (at 5/6 pixel), and is shifted.
349 { 0x00, 0x00, 0x01, 0x05, 0x10, 0x24, 0x39, 0x40, 0x33, 0x1c, 0x0b, 0x03, },
350 };
351
352 for (int y = 0; y < height; ++y) {
353 uint16_t* dstP;
354 size_t dstPDelta;
355 if (doVert) {
356 dstP = dstImage + y;
357 dstPDelta = dstRB;
358 } else {
359 dstP = SkTAddOffset<uint16_t>(dstImage, dstRB * y);
360 dstPDelta = sizeof(uint16_t);
361 }
362
363 const uint8_t* srcP = src.addr8(0, y);
364
365 // TODO: this fir filter implementation is straight forward, but slow.
366 // It should be possible to make it much faster.
367 for (int sample_x = -4; sample_x < sample_width + 4; sample_x += 4) {
368 int fir[LCD_PER_PIXEL] = { 0 };
369 for (int sample_index = std::max(0, sample_x - 4), coeff_index = sample_index - (sample_x - 4)
370 ; sample_index < std::min(sample_x + 8, sample_width)
371 ; ++sample_index, ++coeff_index)
372 {
373 int sample_value = srcP[sample_index];
374 for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) {
375 fir[subpxl_index] += coefficients[subpxl_index][coeff_index] * sample_value;
376 }
377 }
378 for (int subpxl_index = 0; subpxl_index < LCD_PER_PIXEL; ++subpxl_index) {
379 fir[subpxl_index] /= 0x100;
380 fir[subpxl_index] = std::min(fir[subpxl_index], 255);
381 }
382
383 U8CPU r, g, b;
384 if (doBGR) {
385 r = fir[2];
386 g = fir[1];
387 b = fir[0];
388 } else {
389 r = fir[0];
390 g = fir[1];
391 b = fir[2];
392 }
393 if (maskPreBlend.isApplicable()) {
394 r = maskPreBlend.fR[r];
395 g = maskPreBlend.fG[g];
396 b = maskPreBlend.fB[b];
397 }
398#if SK_SHOW_TEXT_BLIT_COVERAGE
399 r = std::max(r, 10); g = std::max(g, 10); b = std::max(b, 10);
400#endif
401 *dstP = SkPack888ToRGB16(r, g, b);
402 dstP = SkTAddOffset<uint16_t>(dstP, dstPDelta);
403 }
404 }
405}
406
407static inline int convert_8_to_1(unsigned byte) {
408 SkASSERT(byte <= 0xFF);
409 return byte >> 7;
410}
411
412static uint8_t pack_8_to_1(const uint8_t alpha[8]) {
413 unsigned bits = 0;
414 for (int i = 0; i < 8; ++i) {
415 bits <<= 1;
416 bits |= convert_8_to_1(alpha[i]);
417 }
418 return SkToU8(bits);
419}
420
421static void packA8ToA1(const SkMask& mask, const uint8_t* src, size_t srcRB) {
422 const int height = mask.fBounds.height();
423 const int width = mask.fBounds.width();
424 const int octs = width >> 3;
425 const int leftOverBits = width & 7;
426
427 uint8_t* dst = mask.fImage;
428 const int dstPad = mask.fRowBytes - SkAlign8(width)/8;
429 SkASSERT(dstPad >= 0);
430
431 SkASSERT(width >= 0);
432 SkASSERT(srcRB >= (size_t)width);
433 const size_t srcPad = srcRB - width;
434
435 for (int y = 0; y < height; ++y) {
436 for (int i = 0; i < octs; ++i) {
437 *dst++ = pack_8_to_1(src);
438 src += 8;
439 }
440 if (leftOverBits > 0) {
441 unsigned bits = 0;
442 int shift = 7;
443 for (int i = 0; i < leftOverBits; ++i, --shift) {
444 bits |= convert_8_to_1(*src++) << shift;
445 }
446 *dst++ = bits;
447 }
448 src += srcPad;
449 dst += dstPad;
450 }
451}
452
453static void generateMask(const SkMask& mask, const SkPath& path,
454 const SkMaskGamma::PreBlend& maskPreBlend,
455 bool doBGR, bool doVert) {
456 SkPaint paint;
457
458 int srcW = mask.fBounds.width();
459 int srcH = mask.fBounds.height();
460 int dstW = srcW;
461 int dstH = srcH;
462 int dstRB = mask.fRowBytes;
463
464 SkMatrix matrix;
465 matrix.setTranslate(-SkIntToScalar(mask.fBounds.fLeft),
466 -SkIntToScalar(mask.fBounds.fTop));
467
468 paint.setAntiAlias(SkMask::kBW_Format != mask.fFormat);
469 switch (mask.fFormat) {
470 case SkMask::kBW_Format:
471 dstRB = 0; // signals we need a copy
472 break;
473 case SkMask::kA8_Format:
474 break;
475 case SkMask::kLCD16_Format:
476 if (doVert) {
477 dstW = 4*dstH - 8;
478 dstH = srcW;
479 matrix.setAll(0, 4, -SkIntToScalar(mask.fBounds.fTop + 1) * 4,
480 1, 0, -SkIntToScalar(mask.fBounds.fLeft),
481 0, 0, 1);
482 } else {
483 dstW = 4*dstW - 8;
484 matrix.setAll(4, 0, -SkIntToScalar(mask.fBounds.fLeft + 1) * 4,
485 0, 1, -SkIntToScalar(mask.fBounds.fTop),
486 0, 0, 1);
487 }
488 dstRB = 0; // signals we need a copy
489 break;
490 default:
491 SkDEBUGFAIL("unexpected mask format");
492 }
493
494 SkRasterClip clip;
495 clip.setRect(SkIRect::MakeWH(dstW, dstH));
496
497 const SkImageInfo info = SkImageInfo::MakeA8(dstW, dstH);
498 SkAutoPixmapStorage dst;
499
500 if (0 == dstRB) {
501 if (!dst.tryAlloc(info)) {
502 // can't allocate offscreen, so empty the mask and return
503 sk_bzero(mask.fImage, mask.computeImageSize());
504 return;
505 }
506 } else {
507 dst.reset(info, mask.fImage, dstRB);
508 }
509 sk_bzero(dst.writable_addr(), dst.computeByteSize());
510
511 SkDraw draw;
512 draw.fDst = dst;
513 draw.fRC = &clip;
514 draw.fMatrix = &matrix;
515 draw.drawPath(path, paint);
516
517 switch (mask.fFormat) {
518 case SkMask::kBW_Format:
519 packA8ToA1(mask, dst.addr8(0, 0), dst.rowBytes());
520 break;
521 case SkMask::kA8_Format:
522 if (maskPreBlend.isApplicable()) {
523 applyLUTToA8Mask(mask, maskPreBlend.fG);
524 }
525 break;
526 case SkMask::kLCD16_Format:
527 pack4xHToLCD16(dst, mask, maskPreBlend, doBGR, doVert);
528 break;
529 default:
530 break;
531 }
532}
533
534void SkScalerContext::getImage(const SkGlyph& origGlyph) {
535 const SkGlyph* glyph = &origGlyph;
536 SkGlyph tmpGlyph{origGlyph.getPackedID()};
537
538 // in case we need to call generateImage on a mask-format that is different
539 // (i.e. larger) than what our caller allocated by looking at origGlyph.
540 SkAutoMalloc tmpGlyphImageStorage;
541
542 if (fMaskFilter) { // restore the prefilter bounds
543
544 // need the original bounds, sans our maskfilter
545 sk_sp<SkMaskFilter> mf = std::move(fMaskFilter);
546 this->getMetrics(&tmpGlyph);
547 fMaskFilter = std::move(mf);
548
549 // we need the prefilter bounds to be <= filter bounds
550 SkASSERT(tmpGlyph.fWidth <= origGlyph.fWidth);
551 SkASSERT(tmpGlyph.fHeight <= origGlyph.fHeight);
552
553 if (tmpGlyph.fMaskFormat == origGlyph.fMaskFormat) {
554 tmpGlyph.fImage = origGlyph.fImage;
555 } else {
556 tmpGlyphImageStorage.reset(tmpGlyph.imageSize());
557 tmpGlyph.fImage = tmpGlyphImageStorage.get();
558 }
559 glyph = &tmpGlyph;
560 }
561
562 if (!fGenerateImageFromPath) {
563 generateImage(*glyph);
564 } else {
565 SkPath devPath;
566 SkMask mask = glyph->mask();
567
568 if (!this->internalGetPath(glyph->getPackedID(), &devPath)) {
569 generateImage(*glyph);
570 } else {
571 SkASSERT(SkMask::kARGB32_Format != origGlyph.fMaskFormat);
572 SkASSERT(SkMask::kARGB32_Format != mask.fFormat);
573 const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
574 const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
575 generateMask(mask, devPath, fPreBlend, doBGR, doVert);
576 }
577 }
578
579 if (fMaskFilter) {
580 // the src glyph image shouldn't be 3D
581 SkASSERT(SkMask::k3D_Format != glyph->fMaskFormat);
582
583 SkMask srcM = glyph->mask(),
584 dstM;
585 SkMatrix matrix;
586
587 fRec.getMatrixFrom2x2(&matrix);
588
589 if (as_MFB(fMaskFilter)->filterMask(&dstM, srcM, matrix, nullptr)) {
590 int width = std::min<int>(origGlyph.fWidth, dstM.fBounds.width());
591 int height = std::min<int>(origGlyph.fHeight, dstM.fBounds.height());
592 int dstRB = origGlyph.rowBytes();
593 int srcRB = dstM.fRowBytes;
594
595 const uint8_t* src = (const uint8_t*)dstM.fImage;
596 uint8_t* dst = (uint8_t*)origGlyph.fImage;
597
598 if (SkMask::k3D_Format == dstM.fFormat) {
599 // we have to copy 3 times as much
600 height *= 3;
601 }
602
603 // clean out our glyph, since it may be larger than dstM
604 //sk_bzero(dst, height * dstRB);
605
606 while (--height >= 0) {
607 memcpy(dst, src, width);
608 src += srcRB;
609 dst += dstRB;
610 }
611 SkMask::FreeImage(dstM.fImage);
612 }
613 }
614}
615
616bool SkScalerContext::getPath(SkPackedGlyphID glyphID, SkPath* path) {
617 return this->internalGetPath(glyphID, path);
618}
619
620void SkScalerContext::getFontMetrics(SkFontMetrics* fm) {
621 SkASSERT(fm);
622 this->generateFontMetrics(fm);
623}
624
625///////////////////////////////////////////////////////////////////////////////
626
627bool SkScalerContext::internalGetPath(SkPackedGlyphID glyphID, SkPath* devPath) {
628 SkPath path;
629 if (!generatePath(glyphID.glyphID(), &path)) {
630 return false;
631 }
632
633 if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
634 SkFixed dx = glyphID.getSubXFixed();
635 SkFixed dy = glyphID.getSubYFixed();
636 if (dx | dy) {
637 path.offset(SkFixedToScalar(dx), SkFixedToScalar(dy));
638 }
639 }
640
641 if (fRec.fFrameWidth > 0 || fPathEffect != nullptr) {
642 // need the path in user-space, with only the point-size applied
643 // so that our stroking and effects will operate the same way they
644 // would if the user had extracted the path themself, and then
645 // called drawPath
646 SkPath localPath;
647 SkMatrix matrix, inverse;
648
649 fRec.getMatrixFrom2x2(&matrix);
650 if (!matrix.invert(&inverse)) {
651 // assume devPath is already empty.
652 return true;
653 }
654 path.transform(inverse, &localPath);
655 // now localPath is only affected by the paint settings, and not the canvas matrix
656
657 SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
658
659 if (fRec.fFrameWidth > 0) {
660 rec.setStrokeStyle(fRec.fFrameWidth,
661 SkToBool(fRec.fFlags & kFrameAndFill_Flag));
662 // glyphs are always closed contours, so cap type is ignored,
663 // so we just pass something.
664 rec.setStrokeParams((SkPaint::Cap)fRec.fStrokeCap,
665 (SkPaint::Join)fRec.fStrokeJoin,
666 fRec.fMiterLimit);
667 }
668
669 if (fPathEffect) {
670 SkPath effectPath;
671 if (fPathEffect->filterPath(&effectPath, localPath, &rec, nullptr)) {
672 localPath.swap(effectPath);
673 }
674 }
675
676 if (rec.needToApply()) {
677 SkPath strokePath;
678 if (rec.applyToPath(&strokePath, localPath)) {
679 localPath.swap(strokePath);
680 }
681 }
682
683 // now return stuff to the caller
684 if (devPath) {
685 localPath.transform(matrix, devPath);
686 }
687 } else { // nothing tricky to do
688 if (devPath) {
689 devPath->swap(path);
690 }
691 }
692
693 if (devPath) {
694 devPath->updateBoundsCache();
695 }
696 return true;
697}
698
699
700void SkScalerContextRec::getMatrixFrom2x2(SkMatrix* dst) const {
701 dst->setAll(fPost2x2[0][0], fPost2x2[0][1], 0,
702 fPost2x2[1][0], fPost2x2[1][1], 0,
703 0, 0, 1);
704}
705
706void SkScalerContextRec::getLocalMatrix(SkMatrix* m) const {
707 *m = SkFontPriv::MakeTextMatrix(fTextSize, fPreScaleX, fPreSkewX);
708}
709
710void SkScalerContextRec::getSingleMatrix(SkMatrix* m) const {
711 this->getLocalMatrix(m);
712
713 // now concat the device matrix
714 SkMatrix deviceMatrix;
715 this->getMatrixFrom2x2(&deviceMatrix);
716 m->postConcat(deviceMatrix);
717}
718
719bool SkScalerContextRec::computeMatrices(PreMatrixScale preMatrixScale, SkVector* s, SkMatrix* sA,
720 SkMatrix* GsA, SkMatrix* G_inv, SkMatrix* A_out)
721{
722 // A is the 'total' matrix.
723 SkMatrix A;
724 this->getSingleMatrix(&A);
725
726 // The caller may find the 'total' matrix useful when dealing directly with EM sizes.
727 if (A_out) {
728 *A_out = A;
729 }
730
731 // GA is the matrix A with rotation removed.
732 SkMatrix GA;
733 bool skewedOrFlipped = A.getSkewX() || A.getSkewY() || A.getScaleX() < 0 || A.getScaleY() < 0;
734 if (skewedOrFlipped) {
735 // QR by Givens rotations. G is Q^T and GA is R. G is rotational (no reflections).
736 // h is where A maps the horizontal baseline.
737 SkPoint h = SkPoint::Make(SK_Scalar1, 0);
738 A.mapPoints(&h, 1);
739
740 // G is the Givens Matrix for A (rotational matrix where GA[0][1] == 0).
741 SkMatrix G;
742 SkComputeGivensRotation(h, &G);
743
744 GA = G;
745 GA.preConcat(A);
746
747 // The 'remainingRotation' is G inverse, which is fairly simple since G is 2x2 rotational.
748 if (G_inv) {
749 G_inv->setAll(
750 G.get(SkMatrix::kMScaleX), -G.get(SkMatrix::kMSkewX), G.get(SkMatrix::kMTransX),
751 -G.get(SkMatrix::kMSkewY), G.get(SkMatrix::kMScaleY), G.get(SkMatrix::kMTransY),
752 G.get(SkMatrix::kMPersp0), G.get(SkMatrix::kMPersp1), G.get(SkMatrix::kMPersp2));
753 }
754 } else {
755 GA = A;
756 if (G_inv) {
757 G_inv->reset();
758 }
759 }
760
761 // If the 'total' matrix is singular, set the 'scale' to something finite and zero the matrices.
762 // All underlying ports have issues with zero text size, so use the matricies to zero.
763 // If one of the scale factors is less than 1/256 then an EM filling square will
764 // never affect any pixels.
765 // If there are any nonfinite numbers in the matrix, bail out and set the matrices to zero.
766 if (SkScalarAbs(GA.get(SkMatrix::kMScaleX)) <= SK_ScalarNearlyZero ||
767 SkScalarAbs(GA.get(SkMatrix::kMScaleY)) <= SK_ScalarNearlyZero ||
768 !GA.isFinite())
769 {
770 s->fX = SK_Scalar1;
771 s->fY = SK_Scalar1;
772 sA->setScale(0, 0);
773 if (GsA) {
774 GsA->setScale(0, 0);
775 }
776 if (G_inv) {
777 G_inv->reset();
778 }
779 return false;
780 }
781
782 // At this point, given GA, create s.
783 switch (preMatrixScale) {
784 case kFull_PreMatrixScale:
785 s->fX = SkScalarAbs(GA.get(SkMatrix::kMScaleX));
786 s->fY = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
787 break;
788 case kVertical_PreMatrixScale: {
789 SkScalar yScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
790 s->fX = yScale;
791 s->fY = yScale;
792 break;
793 }
794 case kVerticalInteger_PreMatrixScale: {
795 SkScalar realYScale = SkScalarAbs(GA.get(SkMatrix::kMScaleY));
796 SkScalar intYScale = SkScalarRoundToScalar(realYScale);
797 if (intYScale == 0) {
798 intYScale = SK_Scalar1;
799 }
800 s->fX = intYScale;
801 s->fY = intYScale;
802 break;
803 }
804 }
805
806 // The 'remaining' matrix sA is the total matrix A without the scale.
807 if (!skewedOrFlipped && (
808 (kFull_PreMatrixScale == preMatrixScale) ||
809 (kVertical_PreMatrixScale == preMatrixScale && A.getScaleX() == A.getScaleY())))
810 {
811 // If GA == A and kFull_PreMatrixScale, sA is identity.
812 // If GA == A and kVertical_PreMatrixScale and A.scaleX == A.scaleY, sA is identity.
813 sA->reset();
814 } else if (!skewedOrFlipped && kVertical_PreMatrixScale == preMatrixScale) {
815 // If GA == A and kVertical_PreMatrixScale, sA.scaleY is SK_Scalar1.
816 sA->reset();
817 sA->setScaleX(A.getScaleX() / s->fY);
818 } else {
819 // TODO: like kVertical_PreMatrixScale, kVerticalInteger_PreMatrixScale with int scales.
820 *sA = A;
821 sA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY));
822 }
823
824 // The 'remainingWithoutRotation' matrix GsA is the non-rotational part of A without the scale.
825 if (GsA) {
826 *GsA = GA;
827 // G is rotational so reorders with the scale.
828 GsA->preScale(SkScalarInvert(s->fX), SkScalarInvert(s->fY));
829 }
830
831 return true;
832}
833
834SkAxisAlignment SkScalerContext::computeAxisAlignmentForHText() const {
835 return fRec.computeAxisAlignmentForHText();
836}
837
838SkAxisAlignment SkScalerContextRec::computeAxisAlignmentForHText() const {
839 // Why fPost2x2 can be used here.
840 // getSingleMatrix multiplies in getLocalMatrix, which consists of
841 // * fTextSize (a scale, which has no effect)
842 // * fPreScaleX (a scale in x, which has no effect)
843 // * fPreSkewX (has no effect, but would on vertical text alignment).
844 // In other words, making the text bigger, stretching it along the
845 // horizontal axis, or fake italicizing it does not move the baseline.
846 if (!SkToBool(fFlags & SkScalerContext::kBaselineSnap_Flag)) {
847 return kNone_SkAxisAlignment;
848 }
849
850 if (0 == fPost2x2[1][0]) {
851 // The x axis is mapped onto the x axis.
852 return kX_SkAxisAlignment;
853 }
854 if (0 == fPost2x2[0][0]) {
855 // The x axis is mapped onto the y axis.
856 return kY_SkAxisAlignment;
857 }
858 return kNone_SkAxisAlignment;
859}
860
861void SkScalerContextRec::setLuminanceColor(SkColor c) {
862 fLumBits = SkMaskGamma::CanonicalColor(
863 SkColorSetRGB(SkColorGetR(c), SkColorGetG(c), SkColorGetB(c)));
864}
865
866extern SkScalerContext* SkCreateColorScalerContext(const SkDescriptor* desc);
867
868std::unique_ptr<SkScalerContext> SkTypeface::createScalerContext(
869 const SkScalerContextEffects& effects, const SkDescriptor* desc) const {
870 auto answer = std::unique_ptr<SkScalerContext>{this->onCreateScalerContext(effects, desc)};
871 SkASSERT(answer != nullptr);
872 return answer;
873}
874
875/*
876 * Return the scalar with only limited fractional precision. Used to consolidate matrices
877 * that vary only slightly when we create our key into the font cache, since the font scaler
878 * typically returns the same looking resuts for tiny changes in the matrix.
879 */
880static SkScalar sk_relax(SkScalar x) {
881 SkScalar n = SkScalarRoundToScalar(x * 1024);
882 return n / 1024.0f;
883}
884
885static SkMask::Format compute_mask_format(const SkFont& font) {
886 switch (font.getEdging()) {
887 case SkFont::Edging::kAlias:
888 return SkMask::kBW_Format;
889 case SkFont::Edging::kAntiAlias:
890 return SkMask::kA8_Format;
891 case SkFont::Edging::kSubpixelAntiAlias:
892 return SkMask::kLCD16_Format;
893 }
894 SkASSERT(false);
895 return SkMask::kA8_Format;
896}
897
898// Beyond this size, LCD doesn't appreciably improve quality, but it always
899// cost more RAM and draws slower, so we set a cap.
900#ifndef SK_MAX_SIZE_FOR_LCDTEXT
901 #define SK_MAX_SIZE_FOR_LCDTEXT 48
902#endif
903
904const SkScalar gMaxSize2ForLCDText = SK_MAX_SIZE_FOR_LCDTEXT * SK_MAX_SIZE_FOR_LCDTEXT;
905
906static bool too_big_for_lcd(const SkScalerContextRec& rec, bool checkPost2x2) {
907 if (checkPost2x2) {
908 SkScalar area = rec.fPost2x2[0][0] * rec.fPost2x2[1][1] -
909 rec.fPost2x2[1][0] * rec.fPost2x2[0][1];
910 area *= rec.fTextSize * rec.fTextSize;
911 return area > gMaxSize2ForLCDText;
912 } else {
913 return rec.fTextSize > SK_MAX_SIZE_FOR_LCDTEXT;
914 }
915}
916
917// The only reason this is not file static is because it needs the context of SkScalerContext to
918// access SkPaint::computeLuminanceColor.
919void SkScalerContext::MakeRecAndEffects(const SkFont& font, const SkPaint& paint,
920 const SkSurfaceProps& surfaceProps,
921 SkScalerContextFlags scalerContextFlags,
922 const SkMatrix& deviceMatrix,
923 SkScalerContextRec* rec,
924 SkScalerContextEffects* effects) {
925 SkASSERT(!deviceMatrix.hasPerspective());
926
927 sk_bzero(rec, sizeof(SkScalerContextRec));
928
929 SkTypeface* typeface = font.getTypefaceOrDefault();
930
931 rec->fFontID = typeface->uniqueID();
932 rec->fTextSize = font.getSize();
933 rec->fPreScaleX = font.getScaleX();
934 rec->fPreSkewX = font.getSkewX();
935
936 bool checkPost2x2 = false;
937
938 const SkMatrix::TypeMask mask = deviceMatrix.getType();
939 if (mask & SkMatrix::kScale_Mask) {
940 rec->fPost2x2[0][0] = sk_relax(deviceMatrix.getScaleX());
941 rec->fPost2x2[1][1] = sk_relax(deviceMatrix.getScaleY());
942 checkPost2x2 = true;
943 } else {
944 rec->fPost2x2[0][0] = rec->fPost2x2[1][1] = SK_Scalar1;
945 }
946 if (mask & SkMatrix::kAffine_Mask) {
947 rec->fPost2x2[0][1] = sk_relax(deviceMatrix.getSkewX());
948 rec->fPost2x2[1][0] = sk_relax(deviceMatrix.getSkewY());
949 checkPost2x2 = true;
950 } else {
951 rec->fPost2x2[0][1] = rec->fPost2x2[1][0] = 0;
952 }
953
954 SkPaint::Style style = paint.getStyle();
955 SkScalar strokeWidth = paint.getStrokeWidth();
956
957 unsigned flags = 0;
958
959 if (font.isEmbolden()) {
960#ifdef SK_USE_FREETYPE_EMBOLDEN
961 flags |= SkScalerContext::kEmbolden_Flag;
962#else
963 SkScalar fakeBoldScale = SkScalarInterpFunc(font.getSize(),
964 kStdFakeBoldInterpKeys,
965 kStdFakeBoldInterpValues,
966 kStdFakeBoldInterpLength);
967 SkScalar extra = font.getSize() * fakeBoldScale;
968
969 if (style == SkPaint::kFill_Style) {
970 style = SkPaint::kStrokeAndFill_Style;
971 strokeWidth = extra; // ignore paint's strokeWidth if it was "fill"
972 } else {
973 strokeWidth += extra;
974 }
975#endif
976 }
977
978 if (style != SkPaint::kFill_Style && strokeWidth > 0) {
979 rec->fFrameWidth = strokeWidth;
980 rec->fMiterLimit = paint.getStrokeMiter();
981 rec->fStrokeJoin = SkToU8(paint.getStrokeJoin());
982 rec->fStrokeCap = SkToU8(paint.getStrokeCap());
983
984 if (style == SkPaint::kStrokeAndFill_Style) {
985 flags |= SkScalerContext::kFrameAndFill_Flag;
986 }
987 } else {
988 rec->fFrameWidth = 0;
989 rec->fMiterLimit = 0;
990 rec->fStrokeJoin = 0;
991 rec->fStrokeCap = 0;
992 }
993
994 rec->fMaskFormat = SkToU8(compute_mask_format(font));
995
996 if (SkMask::kLCD16_Format == rec->fMaskFormat) {
997 if (too_big_for_lcd(*rec, checkPost2x2)) {
998 rec->fMaskFormat = SkMask::kA8_Format;
999 flags |= SkScalerContext::kGenA8FromLCD_Flag;
1000 } else {
1001 SkPixelGeometry geometry = surfaceProps.pixelGeometry();
1002
1003 switch (geometry) {
1004 case kUnknown_SkPixelGeometry:
1005 // eeek, can't support LCD
1006 rec->fMaskFormat = SkMask::kA8_Format;
1007 flags |= SkScalerContext::kGenA8FromLCD_Flag;
1008 break;
1009 case kRGB_H_SkPixelGeometry:
1010 // our default, do nothing.
1011 break;
1012 case kBGR_H_SkPixelGeometry:
1013 flags |= SkScalerContext::kLCD_BGROrder_Flag;
1014 break;
1015 case kRGB_V_SkPixelGeometry:
1016 flags |= SkScalerContext::kLCD_Vertical_Flag;
1017 break;
1018 case kBGR_V_SkPixelGeometry:
1019 flags |= SkScalerContext::kLCD_Vertical_Flag;
1020 flags |= SkScalerContext::kLCD_BGROrder_Flag;
1021 break;
1022 }
1023 }
1024 }
1025
1026 if (font.isEmbeddedBitmaps()) {
1027 flags |= SkScalerContext::kEmbeddedBitmapText_Flag;
1028 }
1029 if (font.isSubpixel()) {
1030 flags |= SkScalerContext::kSubpixelPositioning_Flag;
1031 }
1032 if (font.isForceAutoHinting()) {
1033 flags |= SkScalerContext::kForceAutohinting_Flag;
1034 }
1035 if (font.isLinearMetrics()) {
1036 flags |= SkScalerContext::kLinearMetrics_Flag;
1037 }
1038 if (font.isBaselineSnap()) {
1039 flags |= SkScalerContext::kBaselineSnap_Flag;
1040 }
1041 rec->fFlags = SkToU16(flags);
1042
1043 // these modify fFlags, so do them after assigning fFlags
1044 rec->setHinting(font.getHinting());
1045 rec->setLuminanceColor(SkPaintPriv::ComputeLuminanceColor(paint));
1046
1047 // For now always set the paint gamma equal to the device gamma.
1048 // The math in SkMaskGamma can handle them being different,
1049 // but it requires superluminous masks when
1050 // Ex : deviceGamma(x) < paintGamma(x) and x is sufficiently large.
1051 rec->setDeviceGamma(SK_GAMMA_EXPONENT);
1052 rec->setPaintGamma(SK_GAMMA_EXPONENT);
1053
1054#ifdef SK_GAMMA_CONTRAST
1055 rec->setContrast(SK_GAMMA_CONTRAST);
1056#else
1057 // A value of 0.5 for SK_GAMMA_CONTRAST appears to be a good compromise.
1058 // With lower values small text appears washed out (though correctly so).
1059 // With higher values lcd fringing is worse and the smoothing effect of
1060 // partial coverage is diminished.
1061 rec->setContrast(0.5f);
1062#endif
1063
1064 if (!SkToBool(scalerContextFlags & SkScalerContextFlags::kFakeGamma)) {
1065 rec->ignoreGamma();
1066 }
1067 if (!SkToBool(scalerContextFlags & SkScalerContextFlags::kBoostContrast)) {
1068 rec->setContrast(0);
1069 }
1070
1071 new (effects) SkScalerContextEffects{paint};
1072}
1073
1074SkDescriptor* SkScalerContext::MakeDescriptorForPaths(SkFontID typefaceID,
1075 SkAutoDescriptor* ad) {
1076 SkScalerContextRec rec;
1077 memset((void*)&rec, 0, sizeof(rec));
1078 rec.fFontID = typefaceID;
1079 rec.fTextSize = SkFontPriv::kCanonicalTextSizeForPaths;
1080 rec.fPreScaleX = rec.fPost2x2[0][0] = rec.fPost2x2[1][1] = SK_Scalar1;
1081 return AutoDescriptorGivenRecAndEffects(rec, SkScalerContextEffects(), ad);
1082}
1083
1084SkDescriptor* SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
1085 const SkFont& font, const SkPaint& paint, const SkSurfaceProps& surfaceProps,
1086 SkScalerContextFlags scalerContextFlags, const SkMatrix& deviceMatrix, SkAutoDescriptor* ad,
1087 SkScalerContextEffects* effects)
1088{
1089 SkScalerContextRec rec;
1090 MakeRecAndEffects(font, paint, surfaceProps, scalerContextFlags, deviceMatrix, &rec, effects);
1091 return AutoDescriptorGivenRecAndEffects(rec, *effects, ad);
1092}
1093
1094static size_t calculate_size_and_flatten(const SkScalerContextRec& rec,
1095 const SkScalerContextEffects& effects,
1096 SkBinaryWriteBuffer* effectBuffer) {
1097 size_t descSize = sizeof(rec);
1098 int entryCount = 1;
1099
1100 if (effects.fPathEffect || effects.fMaskFilter) {
1101 if (effects.fPathEffect) { effectBuffer->writeFlattenable(effects.fPathEffect); }
1102 if (effects.fMaskFilter) { effectBuffer->writeFlattenable(effects.fMaskFilter); }
1103 entryCount += 1;
1104 descSize += effectBuffer->bytesWritten();
1105 }
1106
1107 descSize += SkDescriptor::ComputeOverhead(entryCount);
1108 return descSize;
1109}
1110
1111static void generate_descriptor(const SkScalerContextRec& rec,
1112 const SkBinaryWriteBuffer& effectBuffer,
1113 SkDescriptor* desc) {
1114 desc->addEntry(kRec_SkDescriptorTag, sizeof(rec), &rec);
1115
1116 if (effectBuffer.bytesWritten() > 0) {
1117 effectBuffer.writeToMemory(desc->addEntry(kEffects_SkDescriptorTag,
1118 effectBuffer.bytesWritten(),
1119 nullptr));
1120 }
1121
1122 desc->computeChecksum();
1123}
1124
1125SkDescriptor* SkScalerContext::AutoDescriptorGivenRecAndEffects(
1126 const SkScalerContextRec& rec,
1127 const SkScalerContextEffects& effects,
1128 SkAutoDescriptor* ad)
1129{
1130 SkBinaryWriteBuffer buf;
1131
1132 ad->reset(calculate_size_and_flatten(rec, effects, &buf));
1133 generate_descriptor(rec, buf, ad->getDesc());
1134
1135 return ad->getDesc();
1136}
1137
1138std::unique_ptr<SkDescriptor> SkScalerContext::DescriptorGivenRecAndEffects(
1139 const SkScalerContextRec& rec,
1140 const SkScalerContextEffects& effects)
1141{
1142 SkBinaryWriteBuffer buf;
1143
1144 auto desc = SkDescriptor::Alloc(calculate_size_and_flatten(rec, effects, &buf));
1145 generate_descriptor(rec, buf, desc.get());
1146
1147 return desc;
1148}
1149
1150void SkScalerContext::DescriptorBufferGiveRec(const SkScalerContextRec& rec, void* buffer) {
1151 generate_descriptor(rec, SkBinaryWriteBuffer{}, (SkDescriptor*)buffer);
1152}
1153
1154bool SkScalerContext::CheckBufferSizeForRec(const SkScalerContextRec& rec,
1155 const SkScalerContextEffects& effects,
1156 size_t size) {
1157 SkBinaryWriteBuffer buf;
1158 return size >= calculate_size_and_flatten(rec, effects, &buf);
1159}
1160
1161SkScalerContext* SkScalerContext::MakeEmptyContext(
1162 sk_sp<SkTypeface> typeface, const SkScalerContextEffects& effects,
1163 const SkDescriptor* desc) {
1164 class SkScalerContext_Empty : public SkScalerContext {
1165 public:
1166 SkScalerContext_Empty(sk_sp<SkTypeface> typeface, const SkScalerContextEffects& effects,
1167 const SkDescriptor* desc)
1168 : SkScalerContext(std::move(typeface), effects, desc) {}
1169
1170 protected:
1171 unsigned generateGlyphCount() override {
1172 return 0;
1173 }
1174 bool generateAdvance(SkGlyph* glyph) override {
1175 glyph->zeroMetrics();
1176 return true;
1177 }
1178 void generateMetrics(SkGlyph* glyph) override {
1179 glyph->fMaskFormat = fRec.fMaskFormat;
1180 glyph->zeroMetrics();
1181 }
1182 void generateImage(const SkGlyph& glyph) override {}
1183 bool generatePath(SkGlyphID glyph, SkPath* path) override {
1184 path->reset();
1185 return false;
1186 }
1187 void generateFontMetrics(SkFontMetrics* metrics) override {
1188 if (metrics) {
1189 sk_bzero(metrics, sizeof(*metrics));
1190 }
1191 }
1192 };
1193
1194 return new SkScalerContext_Empty{std::move(typeface), effects, desc};
1195}
1196
1197
1198
1199
1200