1 /*
2 * Copyright 2018 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 "src/core/SkGlyphRunPainter.h"
9
10 #if SK_SUPPORT_GPU
11 #include "include/gpu/GrRecordingContext.h"
12 #include "src/gpu/GrCaps.h"
13 #include "src/gpu/GrColorInfo.h"
14 #include "src/gpu/GrDirectContextPriv.h"
15 #include "src/gpu/GrRecordingContextPriv.h"
16 #include "src/gpu/SkGr.h"
17 #include "src/gpu/text/GrSDFTControl.h"
18 #include "src/gpu/text/GrTextBlobRedrawCoordinator.h"
19 #include "src/gpu/v1/SurfaceDrawContext_v1.h"
20 #endif // SK_SUPPORT_GPU
21
22 #include "include/core/SkColorFilter.h"
23 #include "include/core/SkMaskFilter.h"
24 #include "include/core/SkPathEffect.h"
25 #include "include/private/SkTDArray.h"
26 #include "src/core/SkDevice.h"
27 #include "src/core/SkDistanceFieldGen.h"
28 #include "src/core/SkDraw.h"
29 #include "src/core/SkEnumerate.h"
30 #include "src/core/SkFontPriv.h"
31 #include "src/core/SkRasterClip.h"
32 #include "src/core/SkScalerCache.h"
33 #include "src/core/SkStrikeCache.h"
34 #include "src/core/SkStrikeForGPU.h"
35 #include "src/core/SkStrikeSpec.h"
36 #include "src/core/SkTraceEvent.h"
37
38 #include <cinttypes>
39 #include <climits>
40
41 // -- SkGlyphRunListPainter ------------------------------------------------------------------------
SkGlyphRunListPainter(const SkSurfaceProps & props,SkColorType colorType,SkScalerContextFlags flags,SkStrikeForGPUCacheInterface * strikeCache)42 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
43 SkColorType colorType,
44 SkScalerContextFlags flags,
45 SkStrikeForGPUCacheInterface* strikeCache)
46 : fDeviceProps{props}
47 , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
48 , fColorType{colorType}, fScalerContextFlags{flags}
49 , fStrikeCache{strikeCache} {}
50
51 // TODO: unify with code in GrSDFTControl.cpp
compute_scaler_context_flags(const SkColorSpace * cs)52 static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
53 // If we're doing linear blending, then we can disable the gamma hacks.
54 // Otherwise, leave them on. In either case, we still want the contrast boost:
55 // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
56 if (cs && cs->gammaIsLinear()) {
57 return SkScalerContextFlags::kBoostContrast;
58 } else {
59 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
60 }
61 }
62
SkGlyphRunListPainter(const SkSurfaceProps & props,SkColorType colorType,SkColorSpace * cs,SkStrikeForGPUCacheInterface * strikeCache)63 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
64 SkColorType colorType,
65 SkColorSpace* cs,
66 SkStrikeForGPUCacheInterface* strikeCache)
67 : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {}
68
69 #if SK_SUPPORT_GPU
SkGlyphRunListPainter(const SkSurfaceProps & props,const GrColorInfo & csi)70 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props, const GrColorInfo& csi)
71 : SkGlyphRunListPainter(props,
72 kUnknown_SkColorType,
73 compute_scaler_context_flags(csi.colorSpace()),
74 SkStrikeCache::GlobalStrikeCache()) {}
75
SkGlyphRunListPainter(const skgpu::v1::SurfaceDrawContext & sdc)76 SkGlyphRunListPainter::SkGlyphRunListPainter(const skgpu::v1::SurfaceDrawContext& sdc)
77 : SkGlyphRunListPainter{sdc.surfaceProps(), sdc.colorInfo()} {}
78
79 #endif // SK_SUPPORT_GPU
80
drawForBitmapDevice(SkCanvas * canvas,const BitmapDevicePainter * bitmapDevice,const SkGlyphRunList & glyphRunList,const SkPaint & paint,const SkMatrix & deviceMatrix)81 void SkGlyphRunListPainter::drawForBitmapDevice(
82 SkCanvas* canvas, const BitmapDevicePainter* bitmapDevice,
83 const SkGlyphRunList& glyphRunList, const SkPaint& paint, const SkMatrix& deviceMatrix) {
84 ScopedBuffers _ = this->ensureBuffers(glyphRunList);
85
86 // TODO: fStrikeCache is only used for GPU, and some compilers complain about it during the no
87 // gpu build. Remove when SkGlyphRunListPainter is split into GPU and CPU version.
88 (void)fStrikeCache;
89
90 // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
91 // convert the lcd text into A8 text. The props communicates this to the scaler.
92 auto& props = (kN32_SkColorType == fColorType && paint.isSrcOver())
93 ? fDeviceProps
94 : fBitmapFallbackProps;
95
96 SkPoint drawOrigin = glyphRunList.origin();
97 for (auto& glyphRun : glyphRunList) {
98 const SkFont& runFont = glyphRun.font();
99
100 fRejected.setSource(glyphRun.source());
101
102 if (SkStrikeSpec::ShouldDrawAsPath(paint, runFont, deviceMatrix)) {
103
104 auto [strikeSpec, strikeToSourceScale] =
105 SkStrikeSpec::MakePath(runFont, paint, props, fScalerContextFlags);
106
107 auto strike = strikeSpec.findOrCreateStrike();
108
109 fAccepted.startSource(fRejected.source());
110 strike->prepareForPathDrawing(&fAccepted, &fRejected);
111 fRejected.flipRejectsToSource();
112
113 // The paint we draw paths with must have the same anti-aliasing state as the runFont
114 // allowing the paths to have the same edging as the glyph masks.
115 SkPaint pathPaint = paint;
116 pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
117
118 const bool stroking = pathPaint.getStyle() != SkPaint::kFill_Style;
119 const bool hairline = pathPaint.getStrokeWidth() == 0;
120 const bool needsExactCTM = pathPaint.getShader()
121 || pathPaint.getPathEffect()
122 || pathPaint.getMaskFilter()
123 || (stroking && !hairline);
124 if (!needsExactCTM) {
125 for (auto [variant, pos] : fAccepted.accepted()) {
126 const SkPath* path = variant.path();
127 SkMatrix m;
128 SkPoint translate = drawOrigin + pos;
129 m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
130 translate.x(), translate.y());
131 SkAutoCanvasRestore acr(canvas, true);
132 canvas->concat(m);
133 canvas->drawPath(*path, pathPaint);
134 }
135 } else {
136 for (auto [variant, pos] : fAccepted.accepted()) {
137 const SkPath* path = variant.path();
138 SkMatrix m;
139 SkPoint translate = drawOrigin + pos;
140 m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
141 translate.x(), translate.y());
142
143 SkPath deviceOutline;
144 path->transform(m, &deviceOutline);
145 deviceOutline.setIsVolatile(true);
146 canvas->drawPath(deviceOutline, pathPaint);
147 }
148 }
149
150 if (!fRejected.source().empty()) {
151 fAccepted.startSource(fRejected.source());
152 strike->prepareForDrawableDrawing(&fAccepted, &fRejected);
153 fRejected.flipRejectsToSource();
154
155 for (auto [variant, pos] : fAccepted.accepted()) {
156 SkDrawable* drawable = variant.drawable();
157 SkMatrix m;
158 SkPoint translate = drawOrigin + pos;
159 m.setScaleTranslate(strikeToSourceScale, strikeToSourceScale,
160 translate.x(), translate.y());
161 SkAutoCanvasRestore acr(canvas, false);
162 SkRect drawableBounds = drawable->getBounds();
163 m.mapRect(&drawableBounds);
164 canvas->saveLayer(&drawableBounds, &paint);
165 drawable->draw(canvas, &m);
166 }
167 }
168 }
169 if (!fRejected.source().empty() && !deviceMatrix.hasPerspective()) {
170 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
171 runFont, paint, props, fScalerContextFlags, deviceMatrix);
172
173 auto strike = strikeSpec.findOrCreateStrike();
174
175 fAccepted.startBitmapDevice(
176 fRejected.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
177 strike->prepareForDrawingMasksCPU(&fAccepted);
178 fRejected.flipRejectsToSource();
179 bitmapDevice->paintMasks(&fAccepted, paint);
180 }
181 if (!fRejected.source().empty()) {
182 SkMatrix runMatrix = deviceMatrix;
183 runMatrix.preTranslate(drawOrigin.x(), drawOrigin.y());
184 std::vector<SkPoint> sourcePositions;
185
186 // Create a strike is source space to calculate scale information.
187 SkStrikeSpec scaleStrikeSpec = SkStrikeSpec::MakeMask(
188 runFont, paint, props, fScalerContextFlags, SkMatrix::I());
189 SkBulkGlyphMetrics metrics{scaleStrikeSpec};
190
191 auto glyphIDs = fRejected.source().get<0>();
192 auto positions = fRejected.source().get<1>();
193 SkSpan<const SkGlyph*> glyphs = metrics.glyphs(glyphIDs);
194 SkScalar maxScale = SK_ScalarMin;
195
196 // Calculate the scale that makes the longest edge 1:1 with its side in the cache.
197 for (auto [glyph, pos] : SkMakeZip(glyphs, positions)) {
198 SkPoint corners[4];
199 SkPoint srcPos = pos + drawOrigin;
200 // Store off the positions in device space to position the glyphs during drawing.
201 sourcePositions.push_back(srcPos);
202 SkRect rect = glyph->rect();
203 rect.makeOffset(srcPos);
204 runMatrix.mapRectToQuad(corners, rect);
205 // left top -> right top
206 SkScalar scale = (corners[1] - corners[0]).length() / rect.width();
207 maxScale = std::max(maxScale, scale);
208 // right top -> right bottom
209 scale = (corners[2] - corners[1]).length() / rect.height();
210 maxScale = std::max(maxScale, scale);
211 // right bottom -> left bottom
212 scale = (corners[3] - corners[2]).length() / rect.width();
213 maxScale = std::max(maxScale, scale);
214 // left bottom -> left top
215 scale = (corners[0] - corners[3]).length() / rect.height();
216 maxScale = std::max(maxScale, scale);
217 }
218
219 if (maxScale * runFont.getSize() > 256) {
220 maxScale = 256.0f / runFont.getSize();
221 }
222
223 SkMatrix cacheScale = SkMatrix::Scale(maxScale, maxScale);
224 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
225 runFont, paint, props, fScalerContextFlags, cacheScale);
226
227 auto strike = strikeSpec.findOrCreateStrike();
228
229 // Figure out all the positions and packed glyphIDs based on the device matrix.
230 fAccepted.startBitmapDevice(
231 fRejected.source(), drawOrigin, deviceMatrix, strike->roundingSpec());
232
233 strike->prepareForDrawingMasksCPU(&fAccepted);
234 auto variants = fAccepted.accepted().get<0>();
235 for (auto [variant, srcPos] : SkMakeZip(variants, sourcePositions)) {
236 const SkGlyph* glyph = variant.glyph();
237 SkMask mask = glyph->mask();
238 // TODO: is this needed will A8 and BW just work?
239 if (mask.fFormat != SkMask::kARGB32_Format) {
240 continue;
241 }
242 SkBitmap bm;
243 bm.installPixels(SkImageInfo::MakeN32Premul(mask.fBounds.size()),
244 mask.fImage,
245 mask.fRowBytes);
246
247 // Since the glyph in the cache is scaled by maxScale, its top left vector is too
248 // long. Reduce it to find proper positions on the device.
249 SkPoint realPos = srcPos
250 + SkPoint::Make(mask.fBounds.left(), mask.fBounds.top()) * (1.0f/maxScale);
251
252 // Calculate the preConcat matrix for drawBitmap to get the rectangle from the
253 // glyph cache (which is multiplied by maxScale) to land in the right place.
254 SkMatrix translate = SkMatrix::Translate(realPos);
255 translate.preScale(1.0f/maxScale, 1.0f/maxScale);
256
257 // Draw the bitmap using the rect from the scaled cache, and not the source
258 // rectangle for the glyph.
259 bitmapDevice->drawBitmap(
260 bm, translate, nullptr, SkSamplingOptions{SkFilterMode::kLinear},
261 paint);
262 }
263 fRejected.flipRejectsToSource();
264 }
265
266 // TODO: have the mask stage above reject the glyphs that are too big, and handle the
267 // rejects in a more sophisticated stage.
268 }
269 }
270
271 // Use the following in your args.gn to dump telemetry for diagnosing chrome Renderer/GPU
272 // differences.
273 // extra_cflags = ["-D", "SK_TRACE_GLYPH_RUN_PROCESS"]
274
275 #if SK_SUPPORT_GPU
processGlyphRun(SkGlyphRunPainterInterface * process,const SkGlyphRun & glyphRun,const SkMatrix & drawMatrix,const SkPaint & runPaint,const GrSDFTControl & control,const char * tag,uint64_t uniqueID)276 void SkGlyphRunListPainter::processGlyphRun(SkGlyphRunPainterInterface* process,
277 const SkGlyphRun& glyphRun,
278 const SkMatrix& drawMatrix,
279 const SkPaint& runPaint,
280 const GrSDFTControl& control,
281 const char* tag,
282 uint64_t uniqueID) {
283 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
284 SkString msg;
285 msg.appendf("\nStart glyph run processing");
286 if (tag != nullptr) {
287 msg.appendf(" for %s ", tag);
288 if (uniqueID != SK_InvalidUniqueID) {
289 msg.appendf(" uniqueID: %" PRIu64, uniqueID);
290 }
291 }
292 msg.appendf("\n matrix\n");
293 msg.appendf(" %7.3g %7.3g %7.3g\n %7.3g %7.3g %7.3g\n",
294 drawMatrix[0], drawMatrix[1], drawMatrix[2],
295 drawMatrix[3], drawMatrix[4], drawMatrix[5]);
296 #endif
297 ScopedBuffers _ = this->ensureBuffers(glyphRun);
298 fRejected.setSource(glyphRun.source());
299 const SkFont& runFont = glyphRun.font();
300
301 // Only consider using direct or SDFT drawing if not drawing hairlines and not perspective.
302 if ((runPaint.getStyle() != SkPaint::kStroke_Style || runPaint.getStrokeWidth() != 0)
303 && !drawMatrix.hasPerspective()) {
304 SkScalar approximateDeviceTextSize =
305 SkFontPriv::ApproximateTransformedTextSize(runFont, drawMatrix);
306
307 if (control.isSDFT(approximateDeviceTextSize, runPaint)) {
308 // Process SDFT - This should be the .009% case.
309 const auto& [strikeSpec, strikeToSourceScale, matrixRange] =
310 SkStrikeSpec::MakeSDFT(runFont, runPaint, fDeviceProps, drawMatrix, control);
311
312 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
313 msg.appendf(" SDFT case:\n%s", strikeSpec.dump().c_str());
314 #endif
315
316 if (!SkScalarNearlyZero(strikeToSourceScale)) {
317 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
318
319 fAccepted.startSource(fRejected.source());
320 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
321 msg.appendf(" glyphs:(x,y):\n %s\n", fAccepted.dumpInput().c_str());
322 #endif
323 strike->prepareForSDFTDrawing(&fAccepted, &fRejected);
324 fRejected.flipRejectsToSource();
325
326 if (process && !fAccepted.empty()) {
327 // processSourceSDFT must be called even if there are no glyphs to make sure
328 // runs are set correctly.
329 process->processSourceSDFT(fAccepted.accepted(),
330 strike->getUnderlyingStrike(),
331 strikeToSourceScale,
332 runFont,
333 matrixRange);
334 }
335 }
336 }
337
338 if (!fRejected.source().empty()) {
339 // Process masks including ARGB - this should be the 99.99% case.
340 // This will handle medium size emoji that are sharing the run with SDFT drawn text.
341 // If things are too big they will be passed along to the drawing of last resort below.
342 SkStrikeSpec strikeSpec = SkStrikeSpec::MakeMask(
343 runFont, runPaint, fDeviceProps, fScalerContextFlags, drawMatrix);
344
345 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
346 msg.appendf(" Mask case:\n%s", strikeSpec.dump().c_str());
347 #endif
348
349 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
350
351 fAccepted.startGPUDevice(fRejected.source(), drawMatrix, strike->roundingSpec());
352 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
353 msg.appendf(" glyphs:(x,y):\n %s\n", fAccepted.dumpInput().c_str());
354 #endif
355 strike->prepareForMaskDrawing(&fAccepted, &fRejected);
356 fRejected.flipRejectsToSource();
357
358 if (process && !fAccepted.empty()) {
359 // processDeviceMasks must be called even if there are no glyphs to make sure runs
360 // are set correctly.
361 process->processDeviceMasks(fAccepted.accepted(), strike->getUnderlyingStrike());
362 }
363 }
364 }
365
366 // Glyphs are generated in different scales relative to the source space. Masks are drawn
367 // in device space, and SDFT and Paths are draw in a fixed constant space. The
368 // maxDimensionInSourceSpace is used to calculate the factor from strike space to source
369 // space.
370 SkScalar maxDimensionInSourceSpace = 0.0;
371 if (!fRejected.source().empty()) {
372 // Drawable case - handle big things with that have a drawable.
373 auto [strikeSpec, strikeToSourceScale] =
374 SkStrikeSpec::MakePath(runFont, runPaint, fDeviceProps, fScalerContextFlags);
375
376 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
377 msg.appendf(" Drawable case:\n%s", strikeSpec.dump().c_str());
378 #endif
379
380 if (!SkScalarNearlyZero(strikeToSourceScale)) {
381 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
382
383 fAccepted.startSource(fRejected.source());
384 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
385 msg.appendf(" glyphs:(x,y):\n %s\n", fAccepted.dumpInput().c_str());
386 #endif
387 strike->prepareForDrawableDrawing(&fAccepted, &fRejected);
388 fRejected.flipRejectsToSource();
389 auto [minHint, maxHint] = fRejected.maxDimensionHint();
390 maxDimensionInSourceSpace = SkScalarCeilToScalar(maxHint * strikeToSourceScale);
391
392 if (process && !fAccepted.empty()) {
393 // processSourceDrawables must be called even if there are no glyphs to make sure
394 // runs are set correctly.
395 process->processSourceDrawables(fAccepted.accepted(), runFont, strikeToSourceScale);
396 }
397 }
398 }
399 if (!fRejected.source().empty()) {
400 // Path case - handle big things without color and that have a path.
401 auto [strikeSpec, strikeToSourceScale] =
402 SkStrikeSpec::MakePath(runFont, runPaint, fDeviceProps, fScalerContextFlags);
403
404 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
405 msg.appendf(" Path case:\n%s", strikeSpec.dump().c_str());
406 #endif
407
408 if (!SkScalarNearlyZero(strikeToSourceScale)) {
409 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
410
411 fAccepted.startSource(fRejected.source());
412 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
413 msg.appendf(" glyphs:(x,y):\n %s\n", fAccepted.dumpInput().c_str());
414 #endif
415 strike->prepareForPathDrawing(&fAccepted, &fRejected);
416 fRejected.flipRejectsToSource();
417 auto [minHint, maxHint] = fRejected.maxDimensionHint();
418 maxDimensionInSourceSpace = SkScalarCeilToScalar(maxHint * strikeToSourceScale);
419
420 if (process && !fAccepted.empty()) {
421 // processSourcePaths must be called even if there are no glyphs to make sure
422 // runs are set correctly.
423 process->processSourcePaths(fAccepted.accepted(), runFont, strikeToSourceScale);
424 }
425 }
426 }
427
428 if (!fRejected.source().empty() && maxDimensionInSourceSpace != 0) {
429 // Draw of last resort. Scale the bitmap to the screen.
430 auto [strikeSpec, strikeToSourceScale] = SkStrikeSpec::MakeSourceFallback(
431 runFont, runPaint, fDeviceProps,
432 fScalerContextFlags, maxDimensionInSourceSpace);
433
434 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
435 msg.appendf("Transformed case:\n%s", strikeSpec.dump().c_str());
436 #endif
437
438 if (!SkScalarNearlyZero(strikeToSourceScale)) {
439 SkScopedStrikeForGPU strike = strikeSpec.findOrCreateScopedStrike(fStrikeCache);
440
441 fAccepted.startSource(fRejected.source());
442 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
443 msg.appendf("glyphs:(x,y):\n %s\n", fAccepted.dumpInput().c_str());
444 #endif
445 strike->prepareForMaskDrawing(&fAccepted, &fRejected);
446 fRejected.flipRejectsToSource();
447 SkASSERT(fRejected.source().empty());
448
449 if (process && !fAccepted.empty()) {
450 process->processSourceMasks(
451 fAccepted.accepted(), strike->getUnderlyingStrike(), strikeToSourceScale);
452 }
453 }
454 }
455 #if defined(SK_TRACE_GLYPH_RUN_PROCESS)
456 msg.appendf("End glyph run processing");
457 if (tag != nullptr) {
458 msg.appendf(" for %s ", tag);
459 }
460 SkDebugf("%s\n", msg.c_str());
461 #endif
462 }
463 #endif // SK_SUPPORT_GPU
464
ensureBuffers(const SkGlyphRunList & glyphRunList)465 auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers {
466 size_t size = 0;
467 for (const SkGlyphRun& run : glyphRunList) {
468 size = std::max(run.runSize(), size);
469 }
470 return ScopedBuffers(this, size);
471 }
472
ensureBuffers(const SkGlyphRun & glyphRun)473 auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRun& glyphRun) -> ScopedBuffers {
474 return ScopedBuffers(this, glyphRun.runSize());
475 }
476
ScopedBuffers(SkGlyphRunListPainter * painter,size_t size)477 SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, size_t size)
478 : fPainter{painter} {
479 fPainter->fAccepted.ensureSize(size);
480 }
481
~ScopedBuffers()482 SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() {
483 fPainter->fAccepted.reset();
484 fPainter->fRejected.reset();
485 }
486
HalfAxisSampleFreq(bool isSubpixel,SkAxisAlignment axisAlignment)487 SkVector SkGlyphPositionRoundingSpec::HalfAxisSampleFreq(
488 bool isSubpixel, SkAxisAlignment axisAlignment) {
489 if (!isSubpixel) {
490 return {SK_ScalarHalf, SK_ScalarHalf};
491 } else {
492 switch (axisAlignment) {
493 case kX_SkAxisAlignment:
494 return {SkPackedGlyphID::kSubpixelRound, SK_ScalarHalf};
495 case kY_SkAxisAlignment:
496 return {SK_ScalarHalf, SkPackedGlyphID::kSubpixelRound};
497 case kNone_SkAxisAlignment:
498 return {SkPackedGlyphID::kSubpixelRound, SkPackedGlyphID::kSubpixelRound};
499 }
500 }
501
502 // Some compilers need this.
503 return {0, 0};
504 }
505
IgnorePositionMask(bool isSubpixel,SkAxisAlignment axisAlignment)506 SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionMask(
507 bool isSubpixel, SkAxisAlignment axisAlignment) {
508 return SkIPoint::Make((!isSubpixel || axisAlignment == kY_SkAxisAlignment) ? 0 : ~0,
509 (!isSubpixel || axisAlignment == kX_SkAxisAlignment) ? 0 : ~0);
510 }
511
IgnorePositionFieldMask(bool isSubpixel,SkAxisAlignment axisAlignment)512 SkIPoint SkGlyphPositionRoundingSpec::IgnorePositionFieldMask(bool isSubpixel,
513 SkAxisAlignment axisAlignment) {
514 SkIPoint ignoreMask = IgnorePositionMask(isSubpixel, axisAlignment);
515 SkIPoint answer{ignoreMask.x() & SkPackedGlyphID::kXYFieldMask.x(),
516 ignoreMask.y() & SkPackedGlyphID::kXYFieldMask.y()};
517 return answer;
518 }
519
SkGlyphPositionRoundingSpec(bool isSubpixel,SkAxisAlignment axisAlignment)520 SkGlyphPositionRoundingSpec::SkGlyphPositionRoundingSpec(
521 bool isSubpixel,SkAxisAlignment axisAlignment)
522 : halfAxisSampleFreq{HalfAxisSampleFreq(isSubpixel, axisAlignment)}
523 , ignorePositionMask{IgnorePositionMask(isSubpixel, axisAlignment)}
524 , ignorePositionFieldMask {IgnorePositionFieldMask(isSubpixel, axisAlignment)}{ }
525