1 /*
2 * Copyright 2011 Google Inc.
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 #include "src/utils/win/SkDWriteNTDDI_VERSION.h"
8
9 #include "include/core/SkTypes.h"
10 #if defined(SK_BUILD_FOR_WIN)
11
12 #undef GetGlyphIndices
13
14 #include "include/codec/SkCodec.h"
15 #include "include/core/SkBBHFactory.h"
16 #include "include/core/SkBitmap.h"
17 #include "include/core/SkData.h"
18 #include "include/core/SkDrawable.h"
19 #include "include/core/SkFontMetrics.h"
20 #include "include/core/SkGraphics.h"
21 #include "include/core/SkOpenTypeSVGDecoder.h"
22 #include "include/core/SkPath.h"
23 #include "include/core/SkPictureRecorder.h"
24 #include "include/private/base/SkMutex.h"
25 #include "include/private/base/SkTo.h"
26 #include "src/core/SkDraw.h"
27 #include "src/core/SkEndian.h"
28 #include "src/core/SkGlyph.h"
29 #include "src/core/SkMaskGamma.h"
30 #include "src/core/SkMatrixProvider.h"
31 #include "src/core/SkRasterClip.h"
32 #include "src/core/SkScalerContext.h"
33 #include "src/core/SkSharedMutex.h"
34 #include "src/ports/SkScalerContext_win_dw.h"
35 #include "src/ports/SkTypeface_win_dw.h"
36 #include "src/sfnt/SkOTTable_EBLC.h"
37 #include "src/sfnt/SkOTTable_EBSC.h"
38 #include "src/sfnt/SkOTTable_gasp.h"
39 #include "src/sfnt/SkOTTable_maxp.h"
40 #include "src/utils/SkMatrix22.h"
41 #include "src/utils/win/SkDWrite.h"
42 #include "src/utils/win/SkDWriteGeometrySink.h"
43 #include "src/utils/win/SkHRESULT.h"
44 #include "src/utils/win/SkTScopedComPtr.h"
45
46 #include <dwrite.h>
47 #include <dwrite_1.h>
48 #include <dwrite_3.h>
49
50 namespace {
51 static inline const constexpr bool kSkShowTextBlitCoverage = false;
52
53 /* Note:
54 * In versions 8 and 8.1 of Windows, some calls in DWrite are not thread safe.
55 * The mutex returned from maybe_dw_mutex protects the calls that are
56 * problematic.
57 */
maybe_dw_mutex(DWriteFontTypeface & typeface)58 static SkSharedMutex* maybe_dw_mutex(DWriteFontTypeface& typeface) {
59 static SkSharedMutex mutex;
60 return typeface.fDWriteFontFace4 ? nullptr : &mutex;
61 }
62
63 class SK_SCOPED_CAPABILITY Exclusive {
64 public:
65 explicit Exclusive(SkSharedMutex* maybe_lock) SK_ACQUIRE(*maybe_lock)
66 : fLock(maybe_lock) {
67 if (fLock) {
68 fLock->acquire();
69 }
70 }
SK_RELEASE_CAPABILITY()71 ~Exclusive() SK_RELEASE_CAPABILITY() {
72 if (fLock) {
73 fLock->release();
74 }
75 }
76
77 private:
78 SkSharedMutex* fLock;
79 };
80 class SK_SCOPED_CAPABILITY Shared {
81 public:
82 explicit Shared(SkSharedMutex* maybe_lock) SK_ACQUIRE_SHARED(*maybe_lock)
83 : fLock(maybe_lock) {
84 if (fLock) {
85 fLock->acquireShared();
86 }
87 }
88
89 // You would think this should be SK_RELEASE_SHARED_CAPABILITY, but SK_SCOPED_CAPABILITY
90 // doesn't fully understand the difference between shared and exclusive.
91 // Please review https://reviews.llvm.org/D52578 for more information.
SK_RELEASE_CAPABILITY()92 ~Shared() SK_RELEASE_CAPABILITY() {
93 if (fLock) {
94 fLock->releaseShared();
95 }
96 }
97
98 private:
99 SkSharedMutex* fLock;
100 };
101
isLCD(const SkScalerContextRec & rec)102 static bool isLCD(const SkScalerContextRec& rec) {
103 return SkMask::kLCD16_Format == rec.fMaskFormat;
104 }
105
is_hinted(DWriteFontTypeface * typeface)106 static bool is_hinted(DWriteFontTypeface* typeface) {
107 Exclusive l(maybe_dw_mutex(*typeface));
108 AutoTDWriteTable<SkOTTableMaximumProfile> maxp(typeface->fDWriteFontFace.get());
109 if (!maxp.fExists) {
110 return false;
111 }
112 if (maxp.fSize < sizeof(SkOTTableMaximumProfile::Version::TT)) {
113 return false;
114 }
115 if (maxp->version.version != SkOTTableMaximumProfile::Version::TT::VERSION) {
116 return false;
117 }
118 return (0 != maxp->version.tt.maxSizeOfInstructions);
119 }
120
121 /** A GaspRange is inclusive, [min, max]. */
122 struct GaspRange {
123 using Behavior = SkOTTableGridAndScanProcedure::GaspRange::behavior;
GaspRange__anonaec314400111::GaspRange124 GaspRange(int min, int max, int version, Behavior flags)
125 : fMin(min), fMax(max), fVersion(version), fFlags(flags) { }
126 int fMin;
127 int fMax;
128 int fVersion;
129 Behavior fFlags;
130 };
131
get_gasp_range(DWriteFontTypeface * typeface,int size,GaspRange * range)132 bool get_gasp_range(DWriteFontTypeface* typeface, int size, GaspRange* range) {
133 AutoTDWriteTable<SkOTTableGridAndScanProcedure> gasp(typeface->fDWriteFontFace.get());
134 if (!gasp.fExists) {
135 return false;
136 }
137 if (gasp.fSize < sizeof(SkOTTableGridAndScanProcedure)) {
138 return false;
139 }
140 if (gasp->version != SkOTTableGridAndScanProcedure::version0 &&
141 gasp->version != SkOTTableGridAndScanProcedure::version1)
142 {
143 return false;
144 }
145
146 uint16_t numRanges = SkEndianSwap16(gasp->numRanges);
147 if (numRanges > 1024 ||
148 gasp.fSize < sizeof(SkOTTableGridAndScanProcedure) +
149 sizeof(SkOTTableGridAndScanProcedure::GaspRange) * numRanges)
150 {
151 return false;
152 }
153
154 const SkOTTableGridAndScanProcedure::GaspRange* rangeTable =
155 SkTAfter<const SkOTTableGridAndScanProcedure::GaspRange>(gasp.get());
156 int minPPEM = -1;
157 for (uint16_t i = 0; i < numRanges; ++i, ++rangeTable) {
158 int maxPPEM = SkEndianSwap16(rangeTable->maxPPEM);
159 if (minPPEM < size && size <= maxPPEM) {
160 range->fMin = minPPEM + 1;
161 range->fMax = maxPPEM;
162 range->fVersion = SkEndian_SwapBE16(gasp->version);
163 range->fFlags = rangeTable->flags;
164 return true;
165 }
166 minPPEM = maxPPEM;
167 }
168 return false;
169 }
170 /** If the rendering mode for the specified 'size' is gridfit, then place
171 * the gridfit range into 'range'. Otherwise, leave 'range' alone.
172 */
is_gridfit_only(GaspRange::Behavior flags)173 static bool is_gridfit_only(GaspRange::Behavior flags) {
174 return flags.raw.value == GaspRange::Behavior::Raw::GridfitMask;
175 }
176
has_bitmap_strike(DWriteFontTypeface * typeface,GaspRange range)177 static bool has_bitmap_strike(DWriteFontTypeface* typeface, GaspRange range) {
178 Exclusive l(maybe_dw_mutex(*typeface));
179 {
180 AutoTDWriteTable<SkOTTableEmbeddedBitmapLocation> eblc(typeface->fDWriteFontFace.get());
181 if (!eblc.fExists) {
182 return false;
183 }
184 if (eblc.fSize < sizeof(SkOTTableEmbeddedBitmapLocation)) {
185 return false;
186 }
187 if (eblc->version != SkOTTableEmbeddedBitmapLocation::version_initial) {
188 return false;
189 }
190
191 uint32_t numSizes = SkEndianSwap32(eblc->numSizes);
192 if (numSizes > 1024 ||
193 eblc.fSize < sizeof(SkOTTableEmbeddedBitmapLocation) +
194 sizeof(SkOTTableEmbeddedBitmapLocation::BitmapSizeTable) * numSizes)
195 {
196 return false;
197 }
198
199 const SkOTTableEmbeddedBitmapLocation::BitmapSizeTable* sizeTable =
200 SkTAfter<const SkOTTableEmbeddedBitmapLocation::BitmapSizeTable>(eblc.get());
201 for (uint32_t i = 0; i < numSizes; ++i, ++sizeTable) {
202 if (sizeTable->ppemX == sizeTable->ppemY &&
203 range.fMin <= sizeTable->ppemX && sizeTable->ppemX <= range.fMax)
204 {
205 // TODO: determine if we should dig through IndexSubTableArray/IndexSubTable
206 // to determine the actual number of glyphs with bitmaps.
207
208 // TODO: Ensure that the bitmaps actually cover a significant portion of the strike.
209
210 // TODO: Ensure that the bitmaps are bi-level?
211 if (sizeTable->endGlyphIndex >= sizeTable->startGlyphIndex + 3) {
212 return true;
213 }
214 }
215 }
216 }
217
218 {
219 AutoTDWriteTable<SkOTTableEmbeddedBitmapScaling> ebsc(typeface->fDWriteFontFace.get());
220 if (!ebsc.fExists) {
221 return false;
222 }
223 if (ebsc.fSize < sizeof(SkOTTableEmbeddedBitmapScaling)) {
224 return false;
225 }
226 if (ebsc->version != SkOTTableEmbeddedBitmapScaling::version_initial) {
227 return false;
228 }
229
230 uint32_t numSizes = SkEndianSwap32(ebsc->numSizes);
231 if (numSizes > 1024 ||
232 ebsc.fSize < sizeof(SkOTTableEmbeddedBitmapScaling) +
233 sizeof(SkOTTableEmbeddedBitmapScaling::BitmapScaleTable) * numSizes)
234 {
235 return false;
236 }
237
238 const SkOTTableEmbeddedBitmapScaling::BitmapScaleTable* scaleTable =
239 SkTAfter<const SkOTTableEmbeddedBitmapScaling::BitmapScaleTable>(ebsc.get());
240 for (uint32_t i = 0; i < numSizes; ++i, ++scaleTable) {
241 if (scaleTable->ppemX == scaleTable->ppemY &&
242 range.fMin <= scaleTable->ppemX && scaleTable->ppemX <= range.fMax) {
243 // EBSC tables are normally only found in bitmap only fonts.
244 return true;
245 }
246 }
247 }
248
249 return false;
250 }
251
both_zero(SkScalar a,SkScalar b)252 static bool both_zero(SkScalar a, SkScalar b) {
253 return 0 == a && 0 == b;
254 }
255
256 // returns false if there is any non-90-rotation or skew
is_axis_aligned(const SkScalerContextRec & rec)257 static bool is_axis_aligned(const SkScalerContextRec& rec) {
258 return 0 == rec.fPreSkewX &&
259 (both_zero(rec.fPost2x2[0][1], rec.fPost2x2[1][0]) ||
260 both_zero(rec.fPost2x2[0][0], rec.fPost2x2[1][1]));
261 }
262
263 } //namespace
264
SkScalerContext_DW(sk_sp<DWriteFontTypeface> typefaceRef,const SkScalerContextEffects & effects,const SkDescriptor * desc)265 SkScalerContext_DW::SkScalerContext_DW(sk_sp<DWriteFontTypeface> typefaceRef,
266 const SkScalerContextEffects& effects,
267 const SkDescriptor* desc)
268 : SkScalerContext(std::move(typefaceRef), effects, desc)
269 {
270 DWriteFontTypeface* typeface = this->getDWriteTypeface();
271 fGlyphCount = typeface->fDWriteFontFace->GetGlyphCount();
272
273 // In general, all glyphs should use NATURAL_SYMMETRIC
274 // except when bi-level rendering is requested or there are embedded
275 // bi-level bitmaps (and the embedded bitmap flag is set and no rotation).
276 //
277 // DirectWrite's IDWriteFontFace::GetRecommendedRenderingMode does not do
278 // this. As a result, determine the actual size of the text and then see if
279 // there are any embedded bi-level bitmaps of that size. If there are, then
280 // force bitmaps by requesting bi-level rendering.
281 //
282 // FreeType allows for separate ppemX and ppemY, but DirectWrite assumes
283 // square pixels and only uses ppemY. Therefore the transform must track any
284 // non-uniform x-scale.
285 //
286 // Also, rotated glyphs should have the same absolute advance widths as
287 // horizontal glyphs and the subpixel flag should not affect glyph shapes.
288
289 SkVector scale;
290 fRec.computeMatrices(SkScalerContextRec::PreMatrixScale::kVertical, &scale, &fSkXform);
291
292 fXform.m11 = SkScalarToFloat(fSkXform.getScaleX());
293 fXform.m12 = SkScalarToFloat(fSkXform.getSkewY());
294 fXform.m21 = SkScalarToFloat(fSkXform.getSkewX());
295 fXform.m22 = SkScalarToFloat(fSkXform.getScaleY());
296 fXform.dx = 0;
297 fXform.dy = 0;
298
299 // realTextSize is the actual device size we want (as opposed to the size the user requested).
300 // gdiTextSize is the size we request when GDI compatible.
301 // If the scale is negative, this means the matrix will do the flip anyway.
302 const SkScalar realTextSize = scale.fY;
303 // Due to floating point math, the lower bits are suspect. Round carefully.
304 SkScalar gdiTextSize = SkScalarRoundToScalar(realTextSize * 64.0f) / 64.0f;
305 if (gdiTextSize == 0) {
306 gdiTextSize = SK_Scalar1;
307 }
308
309 bool bitmapRequested = SkToBool(fRec.fFlags & SkScalerContext::kEmbeddedBitmapText_Flag);
310 bool treatLikeBitmap = false;
311 bool axisAlignedBitmap = false;
312 if (bitmapRequested) {
313 // When embedded bitmaps are requested, treat the entire range like
314 // a bitmap strike if the range is gridfit only and contains a bitmap.
315 int bitmapPPEM = SkScalarTruncToInt(gdiTextSize);
316 GaspRange range(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
317 if (get_gasp_range(typeface, bitmapPPEM, &range)) {
318 if (!is_gridfit_only(range.fFlags)) {
319 range = GaspRange(bitmapPPEM, bitmapPPEM, 0, GaspRange::Behavior());
320 }
321 }
322 treatLikeBitmap = has_bitmap_strike(typeface, range);
323
324 axisAlignedBitmap = is_axis_aligned(fRec);
325 }
326
327 GaspRange range(0, 0xFFFF, 0, GaspRange::Behavior());
328
329 // If the user requested aliased, do so with aliased compatible metrics.
330 if (SkMask::kBW_Format == fRec.fMaskFormat) {
331 fTextSizeRender = gdiTextSize;
332 fRenderingMode = DWRITE_RENDERING_MODE_ALIASED;
333 fTextureType = DWRITE_TEXTURE_ALIASED_1x1;
334 fTextSizeMeasure = gdiTextSize;
335 fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
336
337 // If we can use a bitmap, use gdi classic rendering and measurement.
338 // This will not always provide a bitmap, but matches expected behavior.
339 } else if (treatLikeBitmap && axisAlignedBitmap) {
340 fTextSizeRender = gdiTextSize;
341 fRenderingMode = DWRITE_RENDERING_MODE_GDI_CLASSIC;
342 fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
343 fTextSizeMeasure = gdiTextSize;
344 fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
345
346 // If rotated but the horizontal text could have used a bitmap,
347 // render high quality rotated glyphs but measure using bitmap metrics.
348 } else if (treatLikeBitmap) {
349 fTextSizeRender = gdiTextSize;
350 fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
351 fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
352 fTextSizeMeasure = gdiTextSize;
353 fMeasuringMode = DWRITE_MEASURING_MODE_GDI_CLASSIC;
354
355 // If the font has a gasp table version 1, use it to determine symmetric rendering.
356 } else if (get_gasp_range(typeface, SkScalarRoundToInt(gdiTextSize), &range) &&
357 range.fVersion >= 1)
358 {
359 fTextSizeRender = realTextSize;
360 fRenderingMode = range.fFlags.field.SymmetricSmoothing
361 ? DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC
362 : DWRITE_RENDERING_MODE_NATURAL;
363 fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
364 fTextSizeMeasure = realTextSize;
365 fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
366
367 // If the requested size is above 20px or there are no bytecode hints, use symmetric rendering.
368 } else if (realTextSize > SkIntToScalar(20) || !is_hinted(typeface)) {
369 fTextSizeRender = realTextSize;
370 fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
371 fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
372 fTextSizeMeasure = realTextSize;
373 fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
374
375 // Fonts with hints, no gasp or gasp version 0, and below 20px get non-symmetric rendering.
376 // Often such fonts have hints which were only tested with GDI ClearType classic.
377 // Some of these fonts rely on drop out control in the y direction in order to be legible.
378 // Tenor Sans
379 // https://fonts.google.com/specimen/Tenor+Sans
380 // Gill Sans W04
381 // https://cdn.leagueoflegends.com/lolkit/1.1.9/resources/fonts/gill-sans-w04-book.woff
382 // https://na.leagueoflegends.com/en/news/game-updates/patch/patch-410-notes
383 // See https://crbug.com/385897
384 } else {
385 fTextSizeRender = gdiTextSize;
386 fRenderingMode = DWRITE_RENDERING_MODE_NATURAL;
387 fTextureType = DWRITE_TEXTURE_CLEARTYPE_3x1;
388 fTextSizeMeasure = realTextSize;
389 fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
390 }
391
392 // DirectWrite2 allows for grayscale hinting.
393 fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE;
394 if (typeface->fFactory2 && typeface->fDWriteFontFace2 &&
395 SkMask::kA8_Format == fRec.fMaskFormat &&
396 !(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag))
397 {
398 // DWRITE_TEXTURE_ALIASED_1x1 is now misnamed, it must also be used with grayscale.
399 fTextureType = DWRITE_TEXTURE_ALIASED_1x1;
400 fAntiAliasMode = DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE;
401 }
402
403 // DirectWrite2 allows hinting to be disabled.
404 fGridFitMode = DWRITE_GRID_FIT_MODE_ENABLED;
405 if (fRec.getHinting() == SkFontHinting::kNone) {
406 fGridFitMode = DWRITE_GRID_FIT_MODE_DISABLED;
407 if (fRenderingMode != DWRITE_RENDERING_MODE_ALIASED) {
408 fRenderingMode = DWRITE_RENDERING_MODE_NATURAL_SYMMETRIC;
409 }
410 }
411
412 if (this->isLinearMetrics()) {
413 fTextSizeMeasure = realTextSize;
414 fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
415 }
416
417 // The GDI measuring modes don't seem to work well with CBDT fonts (DWrite.dll 10.0.18362.836).
418 if (fMeasuringMode != DWRITE_MEASURING_MODE_NATURAL) {
419 constexpr UINT32 CBDTTag = DWRITE_MAKE_OPENTYPE_TAG('C','B','D','T');
420 AutoDWriteTable CBDT(typeface->fDWriteFontFace.get(), CBDTTag);
421 if (CBDT.fExists) {
422 fMeasuringMode = DWRITE_MEASURING_MODE_NATURAL;
423 }
424 }
425 }
426
~SkScalerContext_DW()427 SkScalerContext_DW::~SkScalerContext_DW() {
428 }
429
generateAdvance(SkGlyph * glyph)430 bool SkScalerContext_DW::generateAdvance(SkGlyph* glyph) {
431 glyph->fAdvanceX = 0;
432 glyph->fAdvanceY = 0;
433 uint16_t glyphId = glyph->getGlyphID();
434 DWriteFontTypeface* typeface = this->getDWriteTypeface();
435
436 // DirectWrite treats all out of bounds glyph ids as having the same data as glyph 0.
437 // For consistency with all other backends, treat out of range glyph ids as an error.
438 if (fGlyphCount <= glyphId) {
439 return false;
440 }
441
442 DWRITE_GLYPH_METRICS gm;
443
444 if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
445 DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
446 {
447 Exclusive l(maybe_dw_mutex(*typeface));
448 HRBM(typeface->fDWriteFontFace->GetGdiCompatibleGlyphMetrics(
449 fTextSizeMeasure,
450 1.0f, // pixelsPerDip
451 // This parameter does not act like the lpmat2 parameter to GetGlyphOutlineW.
452 // If it did then GsA here and G_inv below to mapVectors.
453 nullptr,
454 DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode,
455 &glyphId, 1,
456 &gm),
457 "Could not get gdi compatible glyph metrics.");
458 } else {
459 Exclusive l(maybe_dw_mutex(*typeface));
460 HRBM(typeface->fDWriteFontFace->GetDesignGlyphMetrics(&glyphId, 1, &gm),
461 "Could not get design metrics.");
462 }
463
464 DWRITE_FONT_METRICS dwfm;
465 {
466 Shared l(maybe_dw_mutex(*typeface));
467 typeface->fDWriteFontFace->GetMetrics(&dwfm);
468 }
469 SkScalar advanceX = fTextSizeMeasure * gm.advanceWidth / dwfm.designUnitsPerEm;
470
471 SkVector advance = { advanceX, 0 };
472 if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
473 DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
474 {
475 // DirectWrite produced 'compatible' metrics, but while close,
476 // the end result is not always an integer as it would be with GDI.
477 advance.fX = SkScalarRoundToScalar(advance.fX);
478 }
479 fSkXform.mapVectors(&advance, 1);
480
481 glyph->fAdvanceX = SkScalarToFloat(advance.fX);
482 glyph->fAdvanceY = SkScalarToFloat(advance.fY);
483 return true;
484 }
485
getBoundingBox(SkGlyph * glyph,DWRITE_RENDERING_MODE renderingMode,DWRITE_TEXTURE_TYPE textureType,RECT * bbox)486 HRESULT SkScalerContext_DW::getBoundingBox(SkGlyph* glyph,
487 DWRITE_RENDERING_MODE renderingMode,
488 DWRITE_TEXTURE_TYPE textureType,
489 RECT* bbox)
490 {
491 DWriteFontTypeface* typeface = this->getDWriteTypeface();
492
493 //Measure raster size.
494 fXform.dx = SkFixedToFloat(glyph->getSubXFixed());
495 fXform.dy = SkFixedToFloat(glyph->getSubYFixed());
496
497 FLOAT advance = 0;
498
499 UINT16 glyphId = glyph->getGlyphID();
500
501 DWRITE_GLYPH_OFFSET offset;
502 offset.advanceOffset = 0.0f;
503 offset.ascenderOffset = 0.0f;
504
505 DWRITE_GLYPH_RUN run;
506 run.glyphCount = 1;
507 run.glyphAdvances = &advance;
508 run.fontFace = typeface->fDWriteFontFace.get();
509 run.fontEmSize = SkScalarToFloat(fTextSizeRender);
510 run.bidiLevel = 0;
511 run.glyphIndices = &glyphId;
512 run.isSideways = FALSE;
513 run.glyphOffsets = &offset;
514
515 SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
516 {
517 Exclusive l(maybe_dw_mutex(*typeface));
518 // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs.
519 if (typeface->fFactory2 &&
520 (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED ||
521 fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE))
522 {
523 HRM(typeface->fFactory2->CreateGlyphRunAnalysis(
524 &run,
525 &fXform,
526 renderingMode,
527 fMeasuringMode,
528 fGridFitMode,
529 fAntiAliasMode,
530 0.0f, // baselineOriginX,
531 0.0f, // baselineOriginY,
532 &glyphRunAnalysis),
533 "Could not create DW2 glyph run analysis.");
534 } else {
535 HRM(typeface->fFactory->CreateGlyphRunAnalysis(&run,
536 1.0f, // pixelsPerDip,
537 &fXform,
538 renderingMode,
539 fMeasuringMode,
540 0.0f, // baselineOriginX,
541 0.0f, // baselineOriginY,
542 &glyphRunAnalysis),
543 "Could not create glyph run analysis.");
544 }
545 }
546 {
547 Shared l(maybe_dw_mutex(*typeface));
548 HRM(glyphRunAnalysis->GetAlphaTextureBounds(textureType, bbox),
549 "Could not get texture bounds.");
550 }
551 return S_OK;
552 }
553
isColorGlyph(const SkGlyph & glyph)554 bool SkScalerContext_DW::isColorGlyph(const SkGlyph& glyph) {
555 // One would think that with newer DirectWrite that this could be like isPngGlyph
556 // except test for DWRITE_GLYPH_IMAGE_FORMATS_COLR, but that doesn't seem to work.
557
558 SkTScopedComPtr<IDWriteColorGlyphRunEnumerator> colorLayer;
559 return getColorGlyphRun(glyph, &colorLayer);
560 }
561
isPngGlyph(const SkGlyph & glyph)562 bool SkScalerContext_DW::isPngGlyph(const SkGlyph& glyph) {
563 if (!this->getDWriteTypeface()->fDWriteFontFace4) {
564 return false;
565 }
566
567 DWRITE_GLYPH_IMAGE_FORMATS f;
568 IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get();
569 HRBM(fontFace4->GetGlyphImageFormats(glyph.getGlyphID(), 0, UINT32_MAX, &f),
570 "Cannot get glyph image formats.");
571 return f & DWRITE_GLYPH_IMAGE_FORMATS_PNG;
572 }
573
isSVGGlyph(const SkGlyph & glyph)574 bool SkScalerContext_DW::isSVGGlyph(const SkGlyph& glyph) {
575 if (!SkGraphics::GetOpenTypeSVGDecoderFactory() ||
576 !this->getDWriteTypeface()->fDWriteFontFace4)
577 {
578 return false;
579 }
580
581 DWRITE_GLYPH_IMAGE_FORMATS f;
582 IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get();
583 HRBM(fontFace4->GetGlyphImageFormats(glyph.getGlyphID(), 0, UINT32_MAX, &f),
584 "Cannot get glyph image formats.");
585 return f & DWRITE_GLYPH_IMAGE_FORMATS_SVG;
586 }
587
getColorGlyphRun(const SkGlyph & glyph,IDWriteColorGlyphRunEnumerator ** colorGlyph)588 bool SkScalerContext_DW::getColorGlyphRun(const SkGlyph& glyph,
589 IDWriteColorGlyphRunEnumerator** colorGlyph)
590 {
591 FLOAT advance = 0;
592 UINT16 glyphId = glyph.getGlyphID();
593
594 DWRITE_GLYPH_OFFSET offset;
595 offset.advanceOffset = 0.0f;
596 offset.ascenderOffset = 0.0f;
597
598 DWRITE_GLYPH_RUN run;
599 run.glyphCount = 1;
600 run.glyphAdvances = &advance;
601 run.fontFace = this->getDWriteTypeface()->fDWriteFontFace.get();
602 run.fontEmSize = SkScalarToFloat(fTextSizeRender);
603 run.bidiLevel = 0;
604 run.glyphIndices = &glyphId;
605 run.isSideways = FALSE;
606 run.glyphOffsets = &offset;
607
608 HRESULT hr = this->getDWriteTypeface()->fFactory2->TranslateColorGlyphRun(
609 0, 0, &run, nullptr, fMeasuringMode, &fXform, 0, colorGlyph);
610 if (hr == DWRITE_E_NOCOLOR) {
611 return false;
612 }
613 HRBM(hr, "Failed to translate color glyph run");
614 return true;
615 }
616
SetGlyphBounds(SkGlyph * glyph,const SkRect & bounds)617 void SkScalerContext_DW::SetGlyphBounds(SkGlyph* glyph, const SkRect& bounds) {
618 SkIRect ibounds = bounds.roundOut();
619
620 if (!SkTFitsIn<decltype(glyph->fWidth )>(ibounds.width ()) ||
621 !SkTFitsIn<decltype(glyph->fHeight)>(ibounds.height()) ||
622 !SkTFitsIn<decltype(glyph->fTop )>(ibounds.top ()) ||
623 !SkTFitsIn<decltype(glyph->fLeft )>(ibounds.left ()) )
624 {
625 ibounds = SkIRect::MakeEmpty();
626 }
627
628 glyph->fWidth = SkToU16(ibounds.width ());
629 glyph->fHeight = SkToU16(ibounds.height());
630 glyph->fTop = SkToS16(ibounds.top ());
631 glyph->fLeft = SkToS16(ibounds.left ());
632 }
633
generateColorMetrics(SkGlyph * glyph)634 bool SkScalerContext_DW::generateColorMetrics(SkGlyph* glyph) {
635 SkTScopedComPtr<IDWriteColorGlyphRunEnumerator> colorLayers;
636 if (!getColorGlyphRun(*glyph, &colorLayers)) {
637 return false;
638 }
639 SkASSERT(colorLayers.get());
640
641 SkRect bounds = SkRect::MakeEmpty();
642 BOOL hasNextRun = FALSE;
643 while (SUCCEEDED(colorLayers->MoveNext(&hasNextRun)) && hasNextRun) {
644 const DWRITE_COLOR_GLYPH_RUN* colorGlyph;
645 HRBM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run");
646
647 SkPath path;
648 SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
649 HRBM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
650 "Could not create geometry to path converter.");
651 {
652 Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
653 HRBM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
654 colorGlyph->glyphRun.fontEmSize,
655 colorGlyph->glyphRun.glyphIndices,
656 colorGlyph->glyphRun.glyphAdvances,
657 colorGlyph->glyphRun.glyphOffsets,
658 colorGlyph->glyphRun.glyphCount,
659 colorGlyph->glyphRun.isSideways,
660 colorGlyph->glyphRun.bidiLevel % 2, //rtl
661 geometryToPath.get()),
662 "Could not create glyph outline.");
663 }
664 bounds.join(path.getBounds());
665 }
666 SkMatrix matrix = fSkXform;
667 if (this->isSubpixel()) {
668 matrix.postTranslate(SkFixedToScalar(glyph->getSubXFixed()),
669 SkFixedToScalar(glyph->getSubYFixed()));
670 }
671 matrix.mapRect(&bounds);
672 SetGlyphBounds(glyph, bounds);
673 return true;
674 }
675
generateSVGMetrics(SkGlyph * glyph)676 bool SkScalerContext_DW::generateSVGMetrics(SkGlyph* glyph) {
677 SkPictureRecorder recorder;
678 SkRect infiniteRect = SkRect::MakeLTRB(-SK_ScalarInfinity, -SK_ScalarInfinity,
679 SK_ScalarInfinity, SK_ScalarInfinity);
680 sk_sp<SkBBoxHierarchy> bboxh = SkRTreeFactory()();
681 SkCanvas* recordingCanvas = recorder.beginRecording(infiniteRect, bboxh);
682 if (!this->drawSVGGlyphImage(*glyph, *recordingCanvas)) {
683 return false;
684 }
685 sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
686 SkRect bounds = pic->cullRect();
687 SkASSERT(bounds.isFinite());
688
689 SetGlyphBounds(glyph, bounds);
690 return true;
691 }
692
693 namespace {
694 struct Context {
695 SkTScopedComPtr<IDWriteFontFace4> fontFace4;
696 void* glyphDataContext;
Context__anonaec314400211::Context697 Context(IDWriteFontFace4* face4, void* context)
698 : fontFace4(SkRefComPtr(face4))
699 , glyphDataContext(context)
700 {}
701 };
702
ReleaseProc(const void * ptr,void * context)703 static void ReleaseProc(const void* ptr, void* context) {
704 Context* ctx = (Context*)context;
705 ctx->fontFace4->ReleaseGlyphImageData(ctx->glyphDataContext);
706 delete ctx;
707 }
708 }
709
generatePngMetrics(SkGlyph * glyph)710 bool SkScalerContext_DW::generatePngMetrics(SkGlyph* glyph) {
711 SkASSERT(isPngGlyph(*glyph));
712 SkASSERT(this->getDWriteTypeface()->fDWriteFontFace4);
713
714 IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get();
715 DWRITE_GLYPH_IMAGE_DATA glyphData;
716 void* glyphDataContext;
717 HRBM(fontFace4->GetGlyphImageData(glyph->getGlyphID(),
718 fTextSizeRender,
719 DWRITE_GLYPH_IMAGE_FORMATS_PNG,
720 &glyphData,
721 &glyphDataContext),
722 "Glyph image data could not be acquired.");
723
724 Context* context = new Context(fontFace4, glyphDataContext);
725 sk_sp<SkData> data = SkData::MakeWithProc(glyphData.imageData,
726 glyphData.imageDataSize,
727 &ReleaseProc,
728 context);
729
730 std::unique_ptr<SkCodec> codec = SkCodec::MakeFromData(std::move(data));
731 if (!codec) {
732 return false;
733 }
734
735 SkImageInfo info = codec->getInfo();
736 SkRect bounds = SkRect::MakeLTRB(SkIntToScalar(info.bounds().fLeft),
737 SkIntToScalar(info.bounds().fTop),
738 SkIntToScalar(info.bounds().fRight),
739 SkIntToScalar(info.bounds().fBottom));
740
741 SkMatrix matrix = fSkXform;
742 SkScalar scale = fTextSizeRender / glyphData.pixelsPerEm;
743 matrix.preScale(scale, scale);
744 matrix.preTranslate(-glyphData.horizontalLeftOrigin.x, -glyphData.horizontalLeftOrigin.y);
745 if (this->isSubpixel()) {
746 matrix.postTranslate(SkFixedToScalar(glyph->getSubXFixed()),
747 SkFixedToScalar(glyph->getSubYFixed()));
748 }
749 matrix.mapRect(&bounds);
750 SetGlyphBounds(glyph, bounds);
751 return true;
752 }
753
generateMetrics(SkGlyph * glyph,SkArenaAlloc * alloc)754 void SkScalerContext_DW::generateMetrics(SkGlyph* glyph, SkArenaAlloc* alloc) {
755
756 // GetAlphaTextureBounds succeeds but sometimes returns empty bounds like
757 // { 0x80000000, 0x80000000, 0x80000000, 0x80000000 }
758 // for small but not quite zero and large (but not really large) glyphs,
759 // Only set as non-empty if the returned bounds are non-empty.
760 auto glyphCheckAndSetBounds = [](SkGlyph* glyph, const RECT& bbox) {
761 if (bbox.left >= bbox.right || bbox.top >= bbox.bottom) {
762 return false;
763 }
764
765 // We're trying to pack left and top into int16_t,
766 // and width and height into uint16_t, after outsetting by 1.
767 if (!SkIRect::MakeXYWH(-32767, -32767, 65535, 65535).contains(
768 SkIRect::MakeLTRB(bbox.left, bbox.top, bbox.right, bbox.bottom))) {
769 return false;
770 }
771
772 glyph->fWidth = SkToU16(bbox.right - bbox.left);
773 glyph->fHeight = SkToU16(bbox.bottom - bbox.top);
774 glyph->fLeft = SkToS16(bbox.left);
775 glyph->fTop = SkToS16(bbox.top);
776 return true;
777 };
778
779 glyph->fWidth = 0;
780 glyph->fHeight = 0;
781 glyph->fLeft = 0;
782 glyph->fTop = 0;
783
784 if (!this->generateAdvance(glyph)) {
785 return;
786 }
787
788 DWriteFontTypeface* typeface = this->getDWriteTypeface();
789 if (typeface->fIsColorFont) {
790 if (isColorGlyph(*glyph) && generateColorMetrics(glyph)) {
791 glyph->fMaskFormat = SkMask::kARGB32_Format;
792 glyph->fScalerContextBits |= ScalerContextBits::COLR;
793 glyph->setPath(alloc, nullptr, false);
794 return;
795 }
796
797 if (isSVGGlyph(*glyph) && generateSVGMetrics(glyph)) {
798 glyph->fMaskFormat = SkMask::kARGB32_Format;
799 glyph->fScalerContextBits |= ScalerContextBits::SVG;
800 glyph->setPath(alloc, nullptr, false);
801 return;
802 }
803
804 if (isPngGlyph(*glyph) && generatePngMetrics(glyph)) {
805 glyph->fMaskFormat = SkMask::kARGB32_Format;
806 glyph->fScalerContextBits |= ScalerContextBits::PNG;
807 glyph->setPath(alloc, nullptr, false);
808 return;
809 }
810 }
811
812 RECT bbox;
813 HRVM(this->getBoundingBox(glyph, fRenderingMode, fTextureType, &bbox),
814 "Requested bounding box could not be determined.");
815
816 if (glyphCheckAndSetBounds(glyph, bbox)) {
817 return;
818 }
819
820 // GetAlphaTextureBounds succeeds but returns an empty RECT if there are no
821 // glyphs of the specified texture type or it is too big for smoothing.
822 // When this happens, try with the alternate texture type.
823 if (DWRITE_TEXTURE_ALIASED_1x1 != fTextureType ||
824 DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE == fAntiAliasMode)
825 {
826 HRVM(this->getBoundingBox(glyph,
827 DWRITE_RENDERING_MODE_ALIASED,
828 DWRITE_TEXTURE_ALIASED_1x1,
829 &bbox),
830 "Fallback bounding box could not be determined.");
831 if (glyphCheckAndSetBounds(glyph, bbox)) {
832 glyph->fScalerContextBits |= ScalerContextBits::ForceBW;
833 glyph->fMaskFormat = SkMask::kBW_Format;
834 }
835 }
836 // TODO: handle the case where a request for DWRITE_TEXTURE_ALIASED_1x1
837 // fails, and try DWRITE_TEXTURE_CLEARTYPE_3x1.
838
839 // GetAlphaTextureBounds can fail for various reasons. As a fallback, attempt to generate the
840 // metrics from the path
841 SkDEBUGCODE(glyph->fAdvancesBoundsFormatAndInitialPathDone = true;)
842 this->getPath(*glyph, alloc);
843 const SkPath* devPath = glyph->path();
844 if (devPath) {
845 // Sometimes all the above fails. If so, try to create the glyph from path.
846 const SkMask::Format format = glyph->maskFormat();
847 const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
848 const bool a8LCD = SkToBool(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag);
849 const bool hairline = glyph->pathIsHairline();
850 if (GenerateMetricsFromPath(glyph, *devPath, format, doVert, a8LCD, hairline)) {
851 glyph->fScalerContextBits |= ScalerContextBits::PATH;
852 }
853 }
854 }
855
generateFontMetrics(SkFontMetrics * metrics)856 void SkScalerContext_DW::generateFontMetrics(SkFontMetrics* metrics) {
857 if (nullptr == metrics) {
858 return;
859 }
860
861 sk_bzero(metrics, sizeof(*metrics));
862
863 DWRITE_FONT_METRICS dwfm;
864 if (DWRITE_MEASURING_MODE_GDI_CLASSIC == fMeasuringMode ||
865 DWRITE_MEASURING_MODE_GDI_NATURAL == fMeasuringMode)
866 {
867 this->getDWriteTypeface()->fDWriteFontFace->GetGdiCompatibleMetrics(
868 fTextSizeRender,
869 1.0f, // pixelsPerDip
870 &fXform,
871 &dwfm);
872 } else {
873 this->getDWriteTypeface()->fDWriteFontFace->GetMetrics(&dwfm);
874 }
875
876 SkScalar upem = SkIntToScalar(dwfm.designUnitsPerEm);
877
878 metrics->fAscent = -fTextSizeRender * SkIntToScalar(dwfm.ascent) / upem;
879 metrics->fDescent = fTextSizeRender * SkIntToScalar(dwfm.descent) / upem;
880 metrics->fLeading = fTextSizeRender * SkIntToScalar(dwfm.lineGap) / upem;
881 metrics->fXHeight = fTextSizeRender * SkIntToScalar(dwfm.xHeight) / upem;
882 metrics->fCapHeight = fTextSizeRender * SkIntToScalar(dwfm.capHeight) / upem;
883 metrics->fUnderlineThickness = fTextSizeRender * SkIntToScalar(dwfm.underlineThickness) / upem;
884 metrics->fUnderlinePosition = -(fTextSizeRender * SkIntToScalar(dwfm.underlinePosition) / upem);
885 metrics->fStrikeoutThickness = fTextSizeRender * SkIntToScalar(dwfm.strikethroughThickness) / upem;
886 metrics->fStrikeoutPosition = -(fTextSizeRender * SkIntToScalar(dwfm.strikethroughPosition) / upem);
887
888 metrics->fFlags |= SkFontMetrics::kUnderlineThicknessIsValid_Flag;
889 metrics->fFlags |= SkFontMetrics::kUnderlinePositionIsValid_Flag;
890 metrics->fFlags |= SkFontMetrics::kStrikeoutThicknessIsValid_Flag;
891 metrics->fFlags |= SkFontMetrics::kStrikeoutPositionIsValid_Flag;
892
893 SkTScopedComPtr<IDWriteFontFace5> fontFace5;
894 if (SUCCEEDED(this->getDWriteTypeface()->fDWriteFontFace->QueryInterface(&fontFace5))) {
895 if (fontFace5->HasVariations()) {
896 // The bounds are only valid for the default variation.
897 metrics->fFlags |= SkFontMetrics::kBoundsInvalid_Flag;
898 }
899 }
900
901 if (this->getDWriteTypeface()->fDWriteFontFace1.get()) {
902 DWRITE_FONT_METRICS1 dwfm1;
903 this->getDWriteTypeface()->fDWriteFontFace1->GetMetrics(&dwfm1);
904 metrics->fTop = -fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxTop) / upem;
905 metrics->fBottom = -fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxBottom) / upem;
906 metrics->fXMin = fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxLeft) / upem;
907 metrics->fXMax = fTextSizeRender * SkIntToScalar(dwfm1.glyphBoxRight) / upem;
908
909 metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin;
910 return;
911 }
912
913 AutoTDWriteTable<SkOTTableHead> head(this->getDWriteTypeface()->fDWriteFontFace.get());
914 if (head.fExists &&
915 head.fSize >= sizeof(SkOTTableHead) &&
916 head->version == SkOTTableHead::version1)
917 {
918 metrics->fTop = -fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->yMax) / upem;
919 metrics->fBottom = -fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->yMin) / upem;
920 metrics->fXMin = fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->xMin) / upem;
921 metrics->fXMax = fTextSizeRender * (int16_t)SkEndian_SwapBE16(head->xMax) / upem;
922
923 metrics->fMaxCharWidth = metrics->fXMax - metrics->fXMin;
924 return;
925 }
926
927 // The real bounds weren't actually available.
928 metrics->fFlags |= SkFontMetrics::kBoundsInvalid_Flag;
929 metrics->fTop = metrics->fAscent;
930 metrics->fBottom = metrics->fDescent;
931 }
932
933 ///////////////////////////////////////////////////////////////////////////////
934
935 #include "include/private/SkColorData.h"
936
BilevelToBW(const uint8_t * SK_RESTRICT src,const SkGlyph & glyph)937 void SkScalerContext_DW::BilevelToBW(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph) {
938 const int width = glyph.width();
939 const size_t dstRB = (width + 7) >> 3;
940 uint8_t* SK_RESTRICT dst = static_cast<uint8_t*>(glyph.fImage);
941
942 int byteCount = width >> 3;
943 int bitCount = width & 7;
944
945 for (int y = 0; y < glyph.height(); ++y) {
946 if (byteCount > 0) {
947 for (int i = 0; i < byteCount; ++i) {
948 unsigned byte = 0;
949 byte |= src[0] & (1 << 7);
950 byte |= src[1] & (1 << 6);
951 byte |= src[2] & (1 << 5);
952 byte |= src[3] & (1 << 4);
953 byte |= src[4] & (1 << 3);
954 byte |= src[5] & (1 << 2);
955 byte |= src[6] & (1 << 1);
956 byte |= src[7] & (1 << 0);
957 dst[i] = byte;
958 src += 8;
959 }
960 }
961 if (bitCount > 0) {
962 unsigned byte = 0;
963 unsigned mask = 0x80;
964 for (int i = 0; i < bitCount; i++) {
965 byte |= (src[i]) & mask;
966 mask >>= 1;
967 }
968 dst[byteCount] = byte;
969 }
970 src += bitCount;
971 dst += dstRB;
972 }
973
974 if constexpr (kSkShowTextBlitCoverage) {
975 dst = static_cast<uint8_t*>(glyph.fImage);
976 for (unsigned y = 0; y < (unsigned)glyph.height(); y += 2) {
977 for (unsigned x = (y & 0x2); x < (unsigned)glyph.width(); x+=4) {
978 uint8_t& b = dst[(dstRB * y) + (x >> 3)];
979 b = b ^ (1 << (0x7 - (x & 0x7)));
980 }
981 }
982 }
983 }
984
985 template<bool APPLY_PREBLEND>
GrayscaleToA8(const uint8_t * SK_RESTRICT src,const SkGlyph & glyph,const uint8_t * table8)986 void SkScalerContext_DW::GrayscaleToA8(const uint8_t* SK_RESTRICT src,
987 const SkGlyph& glyph,
988 const uint8_t* table8) {
989 const size_t dstRB = glyph.rowBytes();
990 const int width = glyph.width();
991 uint8_t* SK_RESTRICT dst = static_cast<uint8_t*>(glyph.fImage);
992
993 for (int y = 0; y < glyph.height(); y++) {
994 for (int i = 0; i < width; i++) {
995 U8CPU a = *(src++);
996 dst[i] = sk_apply_lut_if<APPLY_PREBLEND>(a, table8);
997 if constexpr (kSkShowTextBlitCoverage) {
998 dst[i] = std::max<U8CPU>(0x30, dst[i]);
999 }
1000 }
1001 dst = SkTAddOffset<uint8_t>(dst, dstRB);
1002 }
1003 }
1004
1005 template<bool APPLY_PREBLEND>
RGBToA8(const uint8_t * SK_RESTRICT src,const SkGlyph & glyph,const uint8_t * table8)1006 void SkScalerContext_DW::RGBToA8(const uint8_t* SK_RESTRICT src,
1007 const SkGlyph& glyph,
1008 const uint8_t* table8) {
1009 const size_t dstRB = glyph.rowBytes();
1010 const int width = glyph.width();
1011 uint8_t* SK_RESTRICT dst = static_cast<uint8_t*>(glyph.fImage);
1012
1013 for (int y = 0; y < glyph.height(); y++) {
1014 for (int i = 0; i < width; i++) {
1015 U8CPU r = *(src++);
1016 U8CPU g = *(src++);
1017 U8CPU b = *(src++);
1018 dst[i] = sk_apply_lut_if<APPLY_PREBLEND>((r + g + b) / 3, table8);
1019 if constexpr (kSkShowTextBlitCoverage) {
1020 dst[i] = std::max<U8CPU>(0x30, dst[i]);
1021 }
1022 }
1023 dst = SkTAddOffset<uint8_t>(dst, dstRB);
1024 }
1025 }
1026
1027 template<bool APPLY_PREBLEND, bool RGB>
RGBToLcd16(const uint8_t * SK_RESTRICT src,const SkGlyph & glyph,const uint8_t * tableR,const uint8_t * tableG,const uint8_t * tableB)1028 void SkScalerContext_DW::RGBToLcd16(const uint8_t* SK_RESTRICT src, const SkGlyph& glyph,
1029 const uint8_t* tableR, const uint8_t* tableG,
1030 const uint8_t* tableB) {
1031 const size_t dstRB = glyph.rowBytes();
1032 const int width = glyph.width();
1033 uint16_t* SK_RESTRICT dst = static_cast<uint16_t*>(glyph.fImage);
1034
1035 for (int y = 0; y < glyph.height(); y++) {
1036 for (int i = 0; i < width; i++) {
1037 U8CPU r, g, b;
1038 if (RGB) {
1039 r = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableR);
1040 g = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableG);
1041 b = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableB);
1042 } else {
1043 b = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableB);
1044 g = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableG);
1045 r = sk_apply_lut_if<APPLY_PREBLEND>(*(src++), tableR);
1046 }
1047 if constexpr (kSkShowTextBlitCoverage) {
1048 r = std::max<U8CPU>(0x30, r);
1049 g = std::max<U8CPU>(0x30, g);
1050 b = std::max<U8CPU>(0x30, b);
1051 }
1052 dst[i] = SkPack888ToRGB16(r, g, b);
1053 }
1054 dst = SkTAddOffset<uint16_t>(dst, dstRB);
1055 }
1056 }
1057
drawDWMask(const SkGlyph & glyph,DWRITE_RENDERING_MODE renderingMode,DWRITE_TEXTURE_TYPE textureType)1058 const void* SkScalerContext_DW::drawDWMask(const SkGlyph& glyph,
1059 DWRITE_RENDERING_MODE renderingMode,
1060 DWRITE_TEXTURE_TYPE textureType)
1061 {
1062 DWriteFontTypeface* typeface = this->getDWriteTypeface();
1063
1064 int sizeNeeded = glyph.width() * glyph.height();
1065 if (DWRITE_TEXTURE_CLEARTYPE_3x1 == textureType) {
1066 sizeNeeded *= 3;
1067 }
1068 if (sizeNeeded > fBits.size()) {
1069 fBits.resize(sizeNeeded);
1070 }
1071
1072 // erase
1073 memset(fBits.begin(), 0, sizeNeeded);
1074
1075 fXform.dx = SkFixedToFloat(glyph.getSubXFixed());
1076 fXform.dy = SkFixedToFloat(glyph.getSubYFixed());
1077
1078 FLOAT advance = 0.0f;
1079
1080 UINT16 index = glyph.getGlyphID();
1081
1082 DWRITE_GLYPH_OFFSET offset;
1083 offset.advanceOffset = 0.0f;
1084 offset.ascenderOffset = 0.0f;
1085
1086 DWRITE_GLYPH_RUN run;
1087 run.glyphCount = 1;
1088 run.glyphAdvances = &advance;
1089 run.fontFace = this->getDWriteTypeface()->fDWriteFontFace.get();
1090 run.fontEmSize = SkScalarToFloat(fTextSizeRender);
1091 run.bidiLevel = 0;
1092 run.glyphIndices = &index;
1093 run.isSideways = FALSE;
1094 run.glyphOffsets = &offset;
1095 {
1096 SkTScopedComPtr<IDWriteGlyphRunAnalysis> glyphRunAnalysis;
1097 {
1098 Exclusive l(maybe_dw_mutex(*typeface));
1099 // IDWriteFactory2::CreateGlyphRunAnalysis is very bad at aliased glyphs.
1100 if (this->getDWriteTypeface()->fFactory2 &&
1101 (fGridFitMode == DWRITE_GRID_FIT_MODE_DISABLED ||
1102 fAntiAliasMode == DWRITE_TEXT_ANTIALIAS_MODE_GRAYSCALE))
1103 {
1104 HRNM(this->getDWriteTypeface()->fFactory2->CreateGlyphRunAnalysis(&run,
1105 &fXform,
1106 renderingMode,
1107 fMeasuringMode,
1108 fGridFitMode,
1109 fAntiAliasMode,
1110 0.0f, // baselineOriginX,
1111 0.0f, // baselineOriginY,
1112 &glyphRunAnalysis),
1113 "Could not create DW2 glyph run analysis.");
1114 } else {
1115 HRNM(this->getDWriteTypeface()->fFactory->CreateGlyphRunAnalysis(&run,
1116 1.0f, // pixelsPerDip,
1117 &fXform,
1118 renderingMode,
1119 fMeasuringMode,
1120 0.0f, // baselineOriginX,
1121 0.0f, // baselineOriginY,
1122 &glyphRunAnalysis),
1123 "Could not create glyph run analysis.");
1124 }
1125 }
1126 //NOTE: this assumes that the glyph has already been measured
1127 //with an exact same glyph run analysis.
1128 RECT bbox;
1129 bbox.left = glyph.left();
1130 bbox.top = glyph.top();
1131 bbox.right = glyph.left() + glyph.width();
1132 bbox.bottom = glyph.top() + glyph.height();
1133 {
1134 Shared l(maybe_dw_mutex(*typeface));
1135 HRNM(glyphRunAnalysis->CreateAlphaTexture(textureType,
1136 &bbox,
1137 fBits.begin(),
1138 sizeNeeded),
1139 "Could not draw mask.");
1140 }
1141 }
1142 return fBits.begin();
1143 }
1144
drawColorGlyphImage(const SkGlyph & glyph,SkCanvas & canvas)1145 bool SkScalerContext_DW::drawColorGlyphImage(const SkGlyph& glyph, SkCanvas& canvas) {
1146 SkTScopedComPtr<IDWriteColorGlyphRunEnumerator> colorLayers;
1147 if (!getColorGlyphRun(glyph, &colorLayers)) {
1148 SkASSERTF(false, "Could not get color layers");
1149 return false;
1150 }
1151
1152 SkPaint paint;
1153 paint.setAntiAlias(fRenderingMode != DWRITE_RENDERING_MODE_ALIASED);
1154
1155 if (this->isSubpixel()) {
1156 canvas.translate(SkFixedToScalar(glyph.getSubXFixed()),
1157 SkFixedToScalar(glyph.getSubYFixed()));
1158 }
1159 canvas.concat(fSkXform);
1160
1161 DWriteFontTypeface* typeface = this->getDWriteTypeface();
1162 size_t paletteEntryCount = typeface->fPaletteEntryCount;
1163 SkColor* palette = typeface->fPalette.get();
1164 BOOL hasNextRun = FALSE;
1165 while (SUCCEEDED(colorLayers->MoveNext(&hasNextRun)) && hasNextRun) {
1166 const DWRITE_COLOR_GLYPH_RUN* colorGlyph;
1167 HRBM(colorLayers->GetCurrentRun(&colorGlyph), "Could not get current color glyph run");
1168
1169 SkColor color;
1170 if (colorGlyph->paletteIndex == 0xffff) {
1171 color = fRec.fForegroundColor;
1172 } else if (colorGlyph->paletteIndex < paletteEntryCount) {
1173 color = palette[colorGlyph->paletteIndex];
1174 } else {
1175 SK_TRACEHR(DWRITE_E_NOCOLOR, "Invalid palette index.");
1176 color = SK_ColorBLACK;
1177 }
1178 paint.setColor(color);
1179
1180 SkPath path;
1181 SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
1182 HRBM(SkDWriteGeometrySink::Create(&path, &geometryToPath),
1183 "Could not create geometry to path converter.");
1184 {
1185 Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
1186 HRBM(colorGlyph->glyphRun.fontFace->GetGlyphRunOutline(
1187 colorGlyph->glyphRun.fontEmSize,
1188 colorGlyph->glyphRun.glyphIndices,
1189 colorGlyph->glyphRun.glyphAdvances,
1190 colorGlyph->glyphRun.glyphOffsets,
1191 colorGlyph->glyphRun.glyphCount,
1192 colorGlyph->glyphRun.isSideways,
1193 colorGlyph->glyphRun.bidiLevel % 2, //rtl
1194 geometryToPath.get()),
1195 "Could not create glyph outline.");
1196 }
1197 canvas.drawPath(path, paint);
1198 }
1199 return true;
1200 }
1201
generateColorGlyphImage(const SkGlyph & glyph)1202 bool SkScalerContext_DW::generateColorGlyphImage(const SkGlyph& glyph) {
1203 SkASSERT(isColorGlyph(glyph));
1204 SkASSERT(glyph.fMaskFormat == SkMask::Format::kARGB32_Format);
1205
1206 SkBitmap dstBitmap;
1207 // TODO: mark this as sRGB when the blits will be sRGB.
1208 dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight,
1209 kN32_SkColorType, kPremul_SkAlphaType),
1210 glyph.rowBytes());
1211 dstBitmap.setPixels(glyph.fImage);
1212
1213 SkCanvas canvas(dstBitmap);
1214 if constexpr (kSkShowTextBlitCoverage) {
1215 canvas.clear(0x33FF0000);
1216 } else {
1217 canvas.clear(SK_ColorTRANSPARENT);
1218 }
1219 canvas.translate(-SkIntToScalar(glyph.fLeft), -SkIntToScalar(glyph.fTop));
1220
1221 return this->drawColorGlyphImage(glyph, canvas);
1222 }
1223
drawSVGGlyphImage(const SkGlyph & glyph,SkCanvas & canvas)1224 bool SkScalerContext_DW::drawSVGGlyphImage(const SkGlyph& glyph, SkCanvas& canvas) {
1225 SkASSERT(isSVGGlyph(glyph));
1226 SkASSERT(this->getDWriteTypeface()->fDWriteFontFace4);
1227
1228 SkGraphics::OpenTypeSVGDecoderFactory svgFactory = SkGraphics::GetOpenTypeSVGDecoderFactory();
1229 if (!svgFactory) {
1230 return false;
1231 }
1232
1233 DWriteFontTypeface* typeface = this->getDWriteTypeface();
1234 IDWriteFontFace4* fontFace4 = typeface->fDWriteFontFace4.get();
1235 DWRITE_GLYPH_IMAGE_DATA glyphData;
1236 void* glyphDataContext;
1237 HRBM(fontFace4->GetGlyphImageData(glyph.getGlyphID(),
1238 fTextSizeRender,
1239 DWRITE_GLYPH_IMAGE_FORMATS_SVG,
1240 &glyphData,
1241 &glyphDataContext),
1242 "Glyph SVG data could not be acquired.");
1243 auto svgDecoder = svgFactory((const uint8_t*)glyphData.imageData, glyphData.imageDataSize);
1244 fontFace4->ReleaseGlyphImageData(glyphDataContext);
1245 if (!svgDecoder) {
1246 return false;
1247 }
1248
1249 size_t paletteEntryCount = typeface->fPaletteEntryCount;
1250 SkColor* palette = typeface->fPalette.get();
1251 int upem = typeface->getUnitsPerEm();
1252
1253 SkMatrix matrix = fSkXform;
1254 SkScalar scale = fTextSizeRender / upem;
1255 matrix.preScale(scale, scale);
1256 matrix.preTranslate(-glyphData.horizontalLeftOrigin.x, -glyphData.horizontalLeftOrigin.y);
1257 if (this->isSubpixel()) {
1258 matrix.postTranslate(SkFixedToScalar(glyph.getSubXFixed()),
1259 SkFixedToScalar(glyph.getSubYFixed()));
1260 }
1261 canvas.concat(matrix);
1262
1263 return svgDecoder->render(canvas, upem, glyph.getGlyphID(),
1264 fRec.fForegroundColor, SkSpan(palette, paletteEntryCount));
1265 }
1266
generateSVGGlyphImage(const SkGlyph & glyph)1267 bool SkScalerContext_DW::generateSVGGlyphImage(const SkGlyph& glyph) {
1268 SkASSERT(isSVGGlyph(glyph));
1269 SkASSERT(glyph.fMaskFormat == SkMask::Format::kARGB32_Format);
1270
1271 SkBitmap dstBitmap;
1272 // TODO: mark this as sRGB when the blits will be sRGB.
1273 dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight,
1274 kN32_SkColorType, kPremul_SkAlphaType),
1275 glyph.rowBytes());
1276 dstBitmap.setPixels(glyph.fImage);
1277
1278 SkCanvas canvas(dstBitmap);
1279 if constexpr (kSkShowTextBlitCoverage) {
1280 canvas.clear(0x33FF0000);
1281 } else {
1282 canvas.clear(SK_ColorTRANSPARENT);
1283 }
1284 canvas.translate(-SkIntToScalar(glyph.fLeft), -SkIntToScalar(glyph.fTop));
1285
1286 return this->drawSVGGlyphImage(glyph, canvas);
1287 }
1288
drawPngGlyphImage(const SkGlyph & glyph,SkCanvas & canvas)1289 bool SkScalerContext_DW::drawPngGlyphImage(const SkGlyph& glyph, SkCanvas& canvas) {
1290 IDWriteFontFace4* fontFace4 = this->getDWriteTypeface()->fDWriteFontFace4.get();
1291 DWRITE_GLYPH_IMAGE_DATA glyphData;
1292 void* glyphDataContext;
1293 HRBM(fontFace4->GetGlyphImageData(glyph.getGlyphID(),
1294 fTextSizeRender,
1295 DWRITE_GLYPH_IMAGE_FORMATS_PNG,
1296 &glyphData,
1297 &glyphDataContext),
1298 "Glyph image data could not be acquired.");
1299 Context* context = new Context(fontFace4, glyphDataContext);
1300 sk_sp<SkData> data = SkData::MakeWithProc(glyphData.imageData,
1301 glyphData.imageDataSize,
1302 &ReleaseProc,
1303 context);
1304 sk_sp<SkImage> image = SkImage::MakeFromEncoded(std::move(data));
1305 if (!image) {
1306 return false;
1307 }
1308
1309 if (this->isSubpixel()) {
1310 canvas.translate(SkFixedToScalar(glyph.getSubXFixed()),
1311 SkFixedToScalar(glyph.getSubYFixed()));
1312 }
1313 canvas.concat(fSkXform);
1314 SkScalar ratio = fTextSizeRender / glyphData.pixelsPerEm;
1315 canvas.scale(ratio, ratio);
1316 canvas.translate(-glyphData.horizontalLeftOrigin.x, -glyphData.horizontalLeftOrigin.y);
1317 canvas.drawImage(image, 0, 0);
1318 return true;
1319 }
1320
generatePngGlyphImage(const SkGlyph & glyph)1321 bool SkScalerContext_DW::generatePngGlyphImage(const SkGlyph& glyph) {
1322 SkASSERT(isPngGlyph(glyph));
1323 SkASSERT(glyph.fMaskFormat == SkMask::Format::kARGB32_Format);
1324 SkASSERT(this->getDWriteTypeface()->fDWriteFontFace4);
1325
1326 SkBitmap dstBitmap;
1327 dstBitmap.setInfo(SkImageInfo::Make(glyph.width(), glyph.height(),
1328 kN32_SkColorType, kPremul_SkAlphaType),
1329 glyph.rowBytes());
1330 dstBitmap.setPixels(glyph.fImage);
1331
1332 SkCanvas canvas(dstBitmap);
1333 canvas.clear(SK_ColorTRANSPARENT);
1334 canvas.translate(-glyph.left(), -glyph.top());
1335
1336 return this->drawPngGlyphImage(glyph, canvas);
1337 }
1338
generateImage(const SkGlyph & glyph)1339 void SkScalerContext_DW::generateImage(const SkGlyph& glyph) {
1340 ScalerContextBits::value_type format = glyph.fScalerContextBits & ScalerContextBits::FormatMask;
1341 if (format == ScalerContextBits::COLR) {
1342 this->generateColorGlyphImage(glyph);
1343 return;
1344 }
1345 if (format == ScalerContextBits::SVG) {
1346 this->generateSVGGlyphImage(glyph);
1347 return;
1348 }
1349 if (format == ScalerContextBits::PNG) {
1350 this->generatePngGlyphImage(glyph);
1351 return;
1352 }
1353 if (format == ScalerContextBits::PATH) {
1354 const SkPath* devPath = glyph.path();
1355 SkASSERT_RELEASE(devPath);
1356 SkMask mask = glyph.mask();
1357 SkASSERT(SkMask::kARGB32_Format != mask.fFormat);
1358 const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
1359 const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
1360 const bool a8LCD = SkToBool(fRec.fFlags & SkScalerContext::kGenA8FromLCD_Flag);
1361 const bool hairline = glyph.pathIsHairline();
1362 GenerateImageFromPath(mask, *devPath, fPreBlend, doBGR, doVert, a8LCD, hairline);
1363 return;
1364 }
1365
1366 //Create the mask.
1367 DWRITE_RENDERING_MODE renderingMode = fRenderingMode;
1368 DWRITE_TEXTURE_TYPE textureType = fTextureType;
1369 if (glyph.fScalerContextBits & ScalerContextBits::ForceBW) {
1370 renderingMode = DWRITE_RENDERING_MODE_ALIASED;
1371 textureType = DWRITE_TEXTURE_ALIASED_1x1;
1372 }
1373 const void* bits = this->drawDWMask(glyph, renderingMode, textureType);
1374 if (!bits) {
1375 sk_bzero(glyph.fImage, glyph.imageSize());
1376 return;
1377 }
1378
1379 //Copy the mask into the glyph.
1380 const uint8_t* src = (const uint8_t*)bits;
1381 if (DWRITE_RENDERING_MODE_ALIASED == renderingMode) {
1382 SkASSERT(SkMask::kBW_Format == glyph.fMaskFormat);
1383 SkASSERT(DWRITE_TEXTURE_ALIASED_1x1 == textureType);
1384 BilevelToBW(src, glyph);
1385 } else if (!isLCD(fRec)) {
1386 if (textureType == DWRITE_TEXTURE_ALIASED_1x1) {
1387 if (fPreBlend.isApplicable()) {
1388 GrayscaleToA8<true>(src, glyph, fPreBlend.fG);
1389 } else {
1390 GrayscaleToA8<false>(src, glyph, fPreBlend.fG);
1391 }
1392 } else {
1393 if (fPreBlend.isApplicable()) {
1394 RGBToA8<true>(src, glyph, fPreBlend.fG);
1395 } else {
1396 RGBToA8<false>(src, glyph, fPreBlend.fG);
1397 }
1398 }
1399 } else {
1400 SkASSERT(SkMask::kLCD16_Format == glyph.fMaskFormat);
1401 if (fPreBlend.isApplicable()) {
1402 if (fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag) {
1403 RGBToLcd16<true, false>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
1404 } else {
1405 RGBToLcd16<true, true>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
1406 }
1407 } else {
1408 if (fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag) {
1409 RGBToLcd16<false, false>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
1410 } else {
1411 RGBToLcd16<false, true>(src, glyph, fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
1412 }
1413 }
1414 }
1415 }
1416
generatePath(const SkGlyph & glyph,SkPath * path)1417 bool SkScalerContext_DW::generatePath(const SkGlyph& glyph, SkPath* path) {
1418 SkASSERT(path);
1419 path->reset();
1420
1421 SkGlyphID glyphID = glyph.getGlyphID();
1422
1423 // DirectWrite treats all out of bounds glyph ids as having the same data as glyph 0.
1424 // For consistency with all other backends, treat out of range glyph ids as an error.
1425 if (fGlyphCount <= glyphID) {
1426 return false;
1427 }
1428
1429 SkTScopedComPtr<IDWriteGeometrySink> geometryToPath;
1430 HRBM(SkDWriteGeometrySink::Create(path, &geometryToPath),
1431 "Could not create geometry to path converter.");
1432 UINT16 glyphId = SkTo<UINT16>(glyphID);
1433 {
1434 Exclusive l(maybe_dw_mutex(*this->getDWriteTypeface()));
1435 //TODO: convert to<->from DIUs? This would make a difference if hinting.
1436 //It may not be needed, it appears that DirectWrite only hints at em size.
1437 HRBM(this->getDWriteTypeface()->fDWriteFontFace->GetGlyphRunOutline(
1438 SkScalarToFloat(fTextSizeRender),
1439 &glyphId,
1440 nullptr, //advances
1441 nullptr, //offsets
1442 1, //num glyphs
1443 FALSE, //sideways
1444 FALSE, //rtl
1445 geometryToPath.get()),
1446 "Could not create glyph outline.");
1447 }
1448
1449 path->transform(fSkXform);
1450 return true;
1451 }
1452
generateDrawable(const SkGlyph & glyph)1453 sk_sp<SkDrawable> SkScalerContext_DW::generateDrawable(const SkGlyph& glyph) {
1454 struct GlyphDrawable : public SkDrawable {
1455 SkScalerContext_DW* fSelf;
1456 SkGlyph fGlyph;
1457 GlyphDrawable(SkScalerContext_DW* self, const SkGlyph& glyph) : fSelf(self), fGlyph(glyph){}
1458 SkRect onGetBounds() override { return fGlyph.rect(); }
1459 size_t onApproximateBytesUsed() override { return sizeof(GlyphDrawable); }
1460 void maybeShowTextBlitCoverage(SkCanvas* canvas) {
1461 if constexpr (kSkShowTextBlitCoverage) {
1462 SkPaint paint;
1463 paint.setColor(0x3300FF00);
1464 paint.setStyle(SkPaint::kFill_Style);
1465 canvas->drawRect(this->onGetBounds(), paint);
1466 }
1467 }
1468 };
1469 struct COLRGlyphDrawable : public GlyphDrawable {
1470 using GlyphDrawable::GlyphDrawable;
1471 void onDraw(SkCanvas* canvas) override {
1472 this->maybeShowTextBlitCoverage(canvas);
1473 fSelf->drawColorGlyphImage(fGlyph, *canvas);
1474 }
1475 };
1476 struct SVGGlyphDrawable : public GlyphDrawable {
1477 using GlyphDrawable::GlyphDrawable;
1478 void onDraw(SkCanvas* canvas) override {
1479 this->maybeShowTextBlitCoverage(canvas);
1480 fSelf->drawSVGGlyphImage(fGlyph, *canvas);
1481 }
1482 };
1483 ScalerContextBits::value_type format = glyph.fScalerContextBits & ScalerContextBits::FormatMask;
1484 if (format == ScalerContextBits::COLR) {
1485 return sk_sp<SkDrawable>(new COLRGlyphDrawable(this, glyph));
1486 }
1487 if (format == ScalerContextBits::SVG) {
1488 return sk_sp<SkDrawable>(new SVGGlyphDrawable(this, glyph));
1489 }
1490 return nullptr;
1491 }
1492
1493 #endif//defined(SK_BUILD_FOR_WIN)
1494