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 "SkGlyphRunPainter.h"
9
10 #if SK_SUPPORT_GPU
11 #include "GrCaps.h"
12 #include "GrColorSpaceInfo.h"
13 #include "GrContextPriv.h"
14 #include "GrRecordingContext.h"
15 #include "GrRecordingContextPriv.h"
16 #include "GrRenderTargetContext.h"
17 #include "SkGr.h"
18 #include "text/GrTextBlobCache.h"
19 #include "text/GrTextContext.h"
20 #endif
21
22 #include "SkColorFilter.h"
23 #include "SkDevice.h"
24 #include "SkDistanceFieldGen.h"
25 #include "SkDraw.h"
26 #include "SkFontPriv.h"
27 #include "SkMaskFilter.h"
28 #include "SkPaintPriv.h"
29 #include "SkPathEffect.h"
30 #include "SkRasterClip.h"
31 #include "SkRemoteGlyphCacheImpl.h"
32 #include "SkStrikeInterface.h"
33 #include "SkStrike.h"
34 #include "SkStrikeCache.h"
35 #include "SkTDArray.h"
36 #include "SkTraceEvent.h"
37
38 // -- SkGlyphCacheCommon ---------------------------------------------------------------------------
39
PixelRounding(bool isSubpixel,SkAxisAlignment axisAlignment)40 SkVector SkStrikeCommon::PixelRounding(bool isSubpixel, SkAxisAlignment axisAlignment) {
41 if (!isSubpixel) {
42 return {SK_ScalarHalf, SK_ScalarHalf};
43 } else {
44 static constexpr SkScalar kSubpixelRounding = SkFixedToScalar(SkGlyph::kSubpixelRound);
45 switch (axisAlignment) {
46 case kX_SkAxisAlignment:
47 return {kSubpixelRounding, SK_ScalarHalf};
48 case kY_SkAxisAlignment:
49 return {SK_ScalarHalf, kSubpixelRounding};
50 case kNone_SkAxisAlignment:
51 return {kSubpixelRounding, kSubpixelRounding};
52 }
53 }
54
55 // Some compilers need this.
56 return {0, 0};
57 }
58
SubpixelLookup(SkAxisAlignment axisAlignment,SkPoint position)59 SkIPoint SkStrikeCommon::SubpixelLookup(SkAxisAlignment axisAlignment, SkPoint position) {
60 // TODO: SkScalarFraction uses truncf to calculate the fraction. This should be floorf.
61 SkFixed lookupX = SkScalarToFixed(SkScalarFraction(position.x())),
62 lookupY = SkScalarToFixed(SkScalarFraction(position.y()));
63
64 // Snap to a given axis if alignment is requested.
65 if (axisAlignment == kX_SkAxisAlignment) {
66 lookupY = 0;
67 } else if (axisAlignment == kY_SkAxisAlignment) {
68 lookupX = 0;
69 }
70
71 return {lookupX, lookupY};
72 }
73
GlyphTooBigForAtlas(const SkGlyph & glyph)74 bool SkStrikeCommon::GlyphTooBigForAtlas(const SkGlyph& glyph) {
75 return glyph.fWidth > kSkSideTooBigForAtlas || glyph.fHeight > kSkSideTooBigForAtlas;
76 }
77
78 // -- SkGlyphRunListPainter ------------------------------------------------------------------------
SkGlyphRunListPainter(const SkSurfaceProps & props,SkColorType colorType,SkScalerContextFlags flags,SkStrikeCacheInterface * strikeCache)79 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
80 SkColorType colorType,
81 SkScalerContextFlags flags,
82 SkStrikeCacheInterface* strikeCache)
83 : fDeviceProps{props}
84 , fBitmapFallbackProps{SkSurfaceProps{props.flags(), kUnknown_SkPixelGeometry}}
85 , fColorType{colorType}, fScalerContextFlags{flags}
86 , fStrikeCache{strikeCache} {}
87
88 // TODO: unify with code in GrTextContext.cpp
compute_scaler_context_flags(const SkColorSpace * cs)89 static SkScalerContextFlags compute_scaler_context_flags(const SkColorSpace* cs) {
90 // If we're doing linear blending, then we can disable the gamma hacks.
91 // Otherwise, leave them on. In either case, we still want the contrast boost:
92 // TODO: Can we be even smarter about mask gamma based on the dest transfer function?
93 if (cs && cs->gammaIsLinear()) {
94 return SkScalerContextFlags::kBoostContrast;
95 } else {
96 return SkScalerContextFlags::kFakeGammaAndBoostContrast;
97 }
98 }
99
SkGlyphRunListPainter(const SkSurfaceProps & props,SkColorType colorType,SkColorSpace * cs,SkStrikeCacheInterface * strikeCache)100 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
101 SkColorType colorType,
102 SkColorSpace* cs,
103 SkStrikeCacheInterface* strikeCache)
104 : SkGlyphRunListPainter(props, colorType, compute_scaler_context_flags(cs), strikeCache) {}
105
106 #if SK_SUPPORT_GPU
SkGlyphRunListPainter(const SkSurfaceProps & props,const GrColorSpaceInfo & csi)107 SkGlyphRunListPainter::SkGlyphRunListPainter(const SkSurfaceProps& props,
108 const GrColorSpaceInfo& csi)
109 : SkGlyphRunListPainter(props,
110 kUnknown_SkColorType,
111 compute_scaler_context_flags(csi.colorSpace()),
112 SkStrikeCache::GlobalStrikeCache()) {}
113
SkGlyphRunListPainter(const GrRenderTargetContext & rtc)114 SkGlyphRunListPainter::SkGlyphRunListPainter(const GrRenderTargetContext& rtc)
115 : SkGlyphRunListPainter{rtc.surfaceProps(), rtc.colorSpaceInfo()} {}
116
117 #endif
118
ShouldDrawAsPath(const SkPaint & paint,const SkFont & font,const SkMatrix & matrix)119 bool SkGlyphRunListPainter::ShouldDrawAsPath(
120 const SkPaint& paint, const SkFont& font, const SkMatrix& matrix) {
121 // hairline glyphs are fast enough so we don't need to cache them
122 if (SkPaint::kStroke_Style == paint.getStyle() && 0 == paint.getStrokeWidth()) {
123 return true;
124 }
125
126 // we don't cache perspective
127 if (matrix.hasPerspective()) {
128 return true;
129 }
130
131 return SkFontPriv::TooBigToUseCache(matrix, SkFontPriv::MakeTextMatrix(font), 1024);
132 }
133
check_glyph_position(SkPoint position)134 static bool check_glyph_position(SkPoint position) {
135 // Prevent glyphs from being drawn outside of or straddling the edge of device space.
136 // Comparisons written a little weirdly so that NaN coordinates are treated safely.
137 auto gt = [](float a, int b) { return !(a <= (float)b); };
138 auto lt = [](float a, int b) { return !(a >= (float)b); };
139 return !(gt(position.fX, INT_MAX - (INT16_MAX + SkTo<int>(UINT16_MAX))) ||
140 lt(position.fX, INT_MIN - (INT16_MIN + 0 /*UINT16_MIN*/)) ||
141 gt(position.fY, INT_MAX - (INT16_MAX + SkTo<int>(UINT16_MAX))) ||
142 lt(position.fY, INT_MIN - (INT16_MIN + 0 /*UINT16_MIN*/)));
143 }
144
create_mask(const SkGlyph & glyph,SkPoint position,const void * image)145 static SkMask create_mask(const SkGlyph& glyph, SkPoint position, const void* image) {
146 SkMask mask;
147 int left = SkScalarFloorToInt(position.fX);
148 int top = SkScalarFloorToInt(position.fY);
149
150 left += glyph.fLeft;
151 top += glyph.fTop;
152
153 int right = left + glyph.fWidth;
154 int bottom = top + glyph.fHeight;
155
156 mask.fBounds.set(left, top, right, bottom);
157 SkASSERT(!mask.fBounds.isEmpty());
158
159 mask.fImage = (uint8_t*)image;
160 mask.fRowBytes = glyph.rowBytes();
161 mask.fFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
162
163 return mask;
164 }
165
drawForBitmapDevice(const SkGlyphRunList & glyphRunList,const SkMatrix & deviceMatrix,const BitmapDevicePainter * bitmapDevice)166 void SkGlyphRunListPainter::drawForBitmapDevice(
167 const SkGlyphRunList& glyphRunList, const SkMatrix& deviceMatrix,
168 const BitmapDevicePainter* bitmapDevice) {
169 ScopedBuffers _ = this->ensureBuffers(glyphRunList);
170
171 const SkPaint& runPaint = glyphRunList.paint();
172 // The bitmap blitters can only draw lcd text to a N32 bitmap in srcOver. Otherwise,
173 // convert the lcd text into A8 text. The props communicates this to the scaler.
174 auto& props = (kN32_SkColorType == fColorType && runPaint.isSrcOver())
175 ? fDeviceProps
176 : fBitmapFallbackProps;
177
178 SkPoint origin = glyphRunList.origin();
179 for (auto& glyphRun : glyphRunList) {
180 const SkFont& runFont = glyphRun.font();
181 auto runSize = glyphRun.runSize();
182
183 if (ShouldDrawAsPath(runPaint, runFont, deviceMatrix)) {
184 SkMatrix::MakeTrans(origin.x(), origin.y()).mapPoints(
185 fPositions, glyphRun.positions().data(), runSize);
186 // setup our std pathPaint, in hopes of getting hits in the cache
187 SkPaint pathPaint(runPaint);
188 SkFont pathFont{runFont};
189 SkScalar textScale = pathFont.setupForAsPaths(&pathPaint);
190
191 auto pathCache = SkStrikeCache::FindOrCreateStrikeExclusive(
192 pathFont, pathPaint, props,
193 fScalerContextFlags, SkMatrix::I());
194
195 SkTDArray<SkPathPos> pathsAndPositions;
196 pathsAndPositions.setReserve(runSize);
197 SkPoint* positionCursor = fPositions;
198 for (auto glyphID : glyphRun.glyphsIDs()) {
199 SkPoint position = *positionCursor++;
200 if (check_glyph_position(position)) {
201 const SkGlyph& glyph = pathCache->getGlyphMetrics(glyphID, {0, 0});
202 if (!glyph.isEmpty()) {
203 const SkPath* path = pathCache->findPath(glyph);
204 if (path != nullptr) {
205 pathsAndPositions.push_back(SkPathPos{path, position});
206 }
207 }
208 }
209 }
210
211 // The paint we draw paths with must have the same anti-aliasing state as the runFont
212 // allowing the paths to have the same edging as the glyph masks.
213 pathPaint = runPaint;
214 pathPaint.setAntiAlias(runFont.hasSomeAntiAliasing());
215
216 bitmapDevice->paintPaths(
217 SkSpan<const SkPathPos>{pathsAndPositions.begin(), pathsAndPositions.size()},
218 textScale, pathPaint);
219 } else {
220 auto cache = SkStrikeCache::FindOrCreateStrikeExclusive(
221 runFont, runPaint, props,
222 fScalerContextFlags, deviceMatrix);
223
224 // Add rounding and origin.
225 SkMatrix matrix = deviceMatrix;
226 matrix.preTranslate(origin.x(), origin.y());
227 SkPoint rounding = cache->rounding();
228 matrix.postTranslate(rounding.x(), rounding.y());
229 matrix.mapPoints(fPositions, glyphRun.positions().data(), runSize);
230
231 SkTDArray<SkMask> masks;
232 masks.setReserve(runSize);
233 const SkPoint* positionCursor = fPositions;
234 for (auto glyphID : glyphRun.glyphsIDs()) {
235 auto position = *positionCursor++;
236 if (check_glyph_position(position)) {
237 const SkGlyph& glyph = cache->getGlyphMetrics(glyphID, position);
238 const void* image;
239 if (!glyph.isEmpty() && (image = cache->findImage(glyph))) {
240 masks.push_back(create_mask(glyph, position, image));
241 }
242 }
243 }
244 bitmapDevice->paintMasks(SkSpan<const SkMask>{masks.begin(), masks.size()}, runPaint);
245 }
246 }
247 }
248
249 // Getting glyphs to the screen in a fallback situation can be complex. Here is the set of
250 // transformations that have to happen. Normally, they would all be accommodated by the font
251 // scaler, but the atlas has an upper limit to the glyphs it can handle. So the GPU is used to
252 // make up the difference from the smaller atlas size to the larger size needed by the final
253 // transform. Here are the transformations that are applied.
254 //
255 // final transform = [view matrix] * [text scale] * [text size]
256 //
257 // There are three cases:
258 // * Go Fast - view matrix is scale and translate, and all the glyphs are small enough
259 // Just scale the positions, and have the glyph cache handle the view matrix transformation.
260 // The text scale is 1.
261 // * It's complicated - view matrix is not scale and translate, and the glyphs are small enough
262 // The glyph cache does not handle the view matrix, but stores the glyphs at the text size
263 // specified by the run paint. The GPU handles the rotation, etc. specified by the view matrix.
264 // The text scale is 1.
265 // * Too big - The glyphs are too big to fit in the atlas
266 // Reduce the text size so the glyphs will fit in the atlas, but don't apply any
267 // transformations from the view matrix. Calculate a text scale based on that reduction. This
268 // scale factor is used to increase the size of the destination rectangles. The destination
269 // rectangles are then scaled, rotated, etc. by the GPU using the view matrix.
processARGBFallback(SkScalar maxSourceGlyphDimension,const SkPaint & runPaint,const SkFont & runFont,const SkMatrix & viewMatrix,SkGlyphRunPainterInterface * process)270 void SkGlyphRunListPainter::processARGBFallback(SkScalar maxSourceGlyphDimension,
271 const SkPaint& runPaint,
272 const SkFont& runFont,
273 const SkMatrix& viewMatrix,
274 SkGlyphRunPainterInterface* process) {
275 SkASSERT(!fARGBGlyphsIDs.empty());
276
277 SkScalar maxScale = viewMatrix.getMaxScale();
278
279 // This is a linear estimate of the longest dimension among all the glyph widths and heights.
280 SkScalar conservativeMaxGlyphDimension = maxSourceGlyphDimension * maxScale;
281
282 // If the situation that the matrix is simple, and all the glyphs are small enough. Go fast!
283 // N.B. If the matrix has scale, that will be reflected in the strike through the viewMatrix
284 // in the useFastPath case.
285 bool useDeviceCache =
286 viewMatrix.isScaleTranslate()
287 && conservativeMaxGlyphDimension <= SkStrikeCommon::kSkSideTooBigForAtlas;
288
289 // A scaled and translated transform is the common case, and is handled directly in fallback.
290 // Even if the transform is scale and translate, fallback must be careful to use glyphs that
291 // fit in the atlas. If a glyph will not fit in the atlas, then the general transform case is
292 // used to render the glyphs.
293 if (useDeviceCache) {
294 // Translate the positions to device space.
295 viewMatrix.mapPoints(fARGBPositions.data(), fARGBPositions.size());
296 for (SkPoint& point : fARGBPositions) {
297 point.fX = SkScalarFloorToScalar(point.fX);
298 point.fY = SkScalarFloorToScalar(point.fY);
299 }
300
301 SkAutoDescriptor ad;
302 SkScalerContextEffects effects;
303 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
304 runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix, &ad, &effects);
305
306 SkScopedStrike strike =
307 fStrikeCache->findOrCreateScopedStrike(
308 *ad.getDesc(), effects, *runFont.getTypefaceOrDefault());
309
310 int drawableGlyphCount = strike->glyphMetrics(fARGBGlyphsIDs.data(),
311 fARGBPositions.data(),
312 fARGBGlyphsIDs.size(),
313 fGlyphPos);
314
315 process->processDeviceFallback(
316 SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(drawableGlyphCount)},
317 strike.get());
318
319 } else {
320 // If the matrix is complicated or if scaling is used to fit the glyphs in the cache,
321 // then this case is used.
322
323 // Subtract 2 to account for the bilerp pad around the glyph
324 SkScalar maxAtlasDimension = SkStrikeCommon::kSkSideTooBigForAtlas - 2;
325
326 SkScalar runFontTextSize = runFont.getSize();
327
328 // Scale the text size down so the long side of all the glyphs will fit in the atlas.
329 SkScalar fallbackTextSize = SkScalarFloorToScalar(
330 (maxAtlasDimension / maxSourceGlyphDimension) * runFontTextSize);
331
332 SkFont fallbackFont{runFont};
333 fallbackFont.setSize(fallbackTextSize);
334
335 // The scale factor to go from strike size to the source size for glyphs.
336 SkScalar fallbackTextScale = runFontTextSize / fallbackTextSize;
337
338 SkAutoDescriptor ad;
339 SkScalerContextEffects effects;
340 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(fallbackFont,
341 runPaint,
342 fDeviceProps,
343 fScalerContextFlags,
344 SkMatrix::I(),
345 &ad,
346 &effects);
347
348 SkScopedStrike strike =
349 fStrikeCache->findOrCreateScopedStrike(
350 *ad.getDesc(), effects, *fallbackFont.getTypefaceOrDefault());
351
352 SkPoint* posCursor = fARGBPositions.data();
353 int glyphCount = 0;
354 for (SkGlyphID glyphID : fARGBGlyphsIDs) {
355 SkPoint pos = *posCursor++;
356 const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
357 fGlyphPos[glyphCount++] = {&glyph, pos};
358 }
359
360 process->processSourceFallback(
361 SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
362 strike.get(),
363 fallbackTextScale,
364 viewMatrix.hasPerspective());
365 }
366 }
367
368 #if SK_SUPPORT_GPU
processGlyphRunList(const SkGlyphRunList & glyphRunList,const SkMatrix & viewMatrix,const SkSurfaceProps & props,bool contextSupportsDistanceFieldText,const GrTextContext::Options & options,SkGlyphRunPainterInterface * process)369 void SkGlyphRunListPainter::processGlyphRunList(const SkGlyphRunList& glyphRunList,
370 const SkMatrix& viewMatrix,
371 const SkSurfaceProps& props,
372 bool contextSupportsDistanceFieldText,
373 const GrTextContext::Options& options,
374 SkGlyphRunPainterInterface* process) {
375
376 SkPoint origin = glyphRunList.origin();
377 const SkPaint& runPaint = glyphRunList.paint();
378
379 for (const auto& glyphRun : glyphRunList) {
380 const SkFont& runFont = glyphRun.font();
381
382 bool useSDFT = GrTextContext::CanDrawAsDistanceFields(
383 runPaint, runFont, viewMatrix, props, contextSupportsDistanceFieldText, options);
384 if (process) {
385 process->startRun(glyphRun, useSDFT);
386 }
387
388 if (useSDFT) {
389 ScopedBuffers _ = this->ensureBuffers(glyphRun);
390 SkScalar maxFallbackDimension{-SK_ScalarInfinity};
391
392 // Setup distance field runPaint and text ratio
393 SkPaint dfPaint = GrTextContext::InitDistanceFieldPaint(runPaint);
394 SkScalar cacheToSourceScale;
395 SkFont dfFont = GrTextContext::InitDistanceFieldFont(
396 runFont, viewMatrix, options, &cacheToSourceScale);
397 // Fake-gamma and subpixel antialiasing are applied in the shader, so we ignore the
398 // passed-in scaler context flags. (It's only used when we fall-back to bitmap text).
399 SkScalerContextFlags flags = SkScalerContextFlags::kNone;
400
401 SkScalar minScale, maxScale;
402 std::tie(minScale, maxScale) = GrTextContext::InitDistanceFieldMinMaxScale(
403 runFont.getSize(), viewMatrix, options);
404
405 SkAutoDescriptor ad;
406 SkScalerContextEffects effects;
407 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
408 dfFont, dfPaint, fDeviceProps, flags, SkMatrix::I(), &ad, &effects);
409 SkScopedStrike strike =
410 fStrikeCache->findOrCreateScopedStrike(
411 *ad.getDesc(), effects, *dfFont.getTypefaceOrDefault());
412
413 std::vector<SkGlyphPos> paths;
414
415 int glyphCount = 0;
416 const SkPoint* positionCursor = glyphRun.positions().data();
417 for (auto glyphID : glyphRun.glyphsIDs()) {
418 const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
419 SkPoint glyphPos = origin + *positionCursor++;
420 if (!glyph.isEmpty()) {
421 if (glyph.fMaskFormat == SkMask::kSDF_Format) {
422 if (!SkStrikeCommon::GlyphTooBigForAtlas(glyph)) {
423 // If the glyph is not empty, then it will have a pointer to SDF data.
424 fGlyphPos[glyphCount++] = {&glyph, glyphPos};
425 } else {
426 if (strike->decideCouldDrawFromPath(glyph)) {
427 paths.push_back({&glyph, glyphPos});
428 }
429 }
430 } else {
431 SkASSERT(glyph.fMaskFormat == SkMask::kARGB32_Format);
432 SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
433 maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
434 fARGBGlyphsIDs.push_back(glyphID);
435 fARGBPositions.push_back(glyphPos);
436 }
437 }
438 }
439
440 if (process) {
441 if (glyphCount > 0) {
442 bool hasWCoord = viewMatrix.hasPerspective()
443 || options.fDistanceFieldVerticesAlwaysHaveW;
444 process->processSourceSDFT(
445 SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
446 strike.get(),
447 runFont,
448 cacheToSourceScale,
449 minScale,
450 maxScale,
451 hasWCoord);
452 }
453
454 if (!paths.empty()) {
455 process->processSourcePaths(
456 SkSpan<const SkGlyphPos>{paths}, strike.get(), cacheToSourceScale);
457 }
458
459 {
460 // fGlyphPos will be reused here.
461 if (!fARGBGlyphsIDs.empty()) {
462 this->processARGBFallback(maxFallbackDimension * cacheToSourceScale,
463 runPaint, runFont, viewMatrix, process);
464 }
465 }
466 }
467 } else if (SkGlyphRunListPainter::ShouldDrawAsPath(runPaint, runFont, viewMatrix)) {
468 ScopedBuffers _ = this->ensureBuffers(glyphRun);
469 SkScalar maxFallbackDimension{-SK_ScalarInfinity};
470
471 // setup our std runPaint, in hopes of getting hits in the cache
472 SkPaint pathPaint{runPaint};
473 SkFont pathFont{runFont};
474
475 // The factor to get from the size stored in the strike to the size needed for
476 // the source.
477 SkScalar strikeToSourceRatio = pathFont.setupForAsPaths(&pathPaint);
478
479 SkAutoDescriptor ad;
480 SkScalerContextEffects effects;
481 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(pathFont,
482 pathPaint,
483 fDeviceProps,
484 fScalerContextFlags,
485 SkMatrix::I(),
486 &ad,
487 &effects);
488 SkScopedStrike strike =
489 fStrikeCache->findOrCreateScopedStrike(
490 *ad.getDesc(), effects,*pathFont.getTypefaceOrDefault());
491
492 int glyphCount = 0;
493 const SkPoint* positionCursor = glyphRun.positions().data();
494 for (auto glyphID : glyphRun.glyphsIDs()) {
495 SkPoint glyphPos = origin + *positionCursor++;
496
497 // Use outline from {0, 0} because all transforms including subpixel translation
498 // happen during drawing.
499 const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, {0, 0});
500 if (!glyph.isEmpty()) {
501 if (glyph.fMaskFormat != SkMask::kARGB32_Format) {
502 if (strike->decideCouldDrawFromPath(glyph)) {
503 fGlyphPos[glyphCount++] = {&glyph, glyphPos};
504 }
505 } else {
506 SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
507 maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
508 fARGBGlyphsIDs.push_back(glyphID);
509 fARGBPositions.push_back(glyphPos);
510 }
511 }
512 }
513
514 if (process) {
515 if (glyphCount > 0) {
516 process->processSourcePaths(
517 SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphCount)},
518 strike.get(),
519 strikeToSourceRatio);
520 }
521
522 // fGlyphPos will be reused here.
523 if (!fARGBGlyphsIDs.empty()) {
524 this->processARGBFallback(maxFallbackDimension * strikeToSourceRatio,
525 runPaint, runFont, viewMatrix, process);
526 }
527 }
528 } else {
529
530 SkAutoDescriptor ad;
531 SkScalerContextEffects effects;
532
533 SkScalerContext::CreateDescriptorAndEffectsUsingPaint(
534 runFont, runPaint, fDeviceProps, fScalerContextFlags, viewMatrix, &ad,
535 &effects);
536
537 SkTypeface* typeface = runFont.getTypefaceOrDefault();
538 SkScopedStrike strike =
539 fStrikeCache->findOrCreateScopedStrike(*ad.getDesc(), effects, *typeface);
540
541 ScopedBuffers _ = this->ensureBuffers(glyphRun);
542 SkScalar maxFallbackDimension{-SK_ScalarInfinity};
543
544 SkMatrix mapping = viewMatrix;
545 mapping.preTranslate(origin.x(), origin.y());
546 SkVector rounding = strike->rounding();
547 mapping.postTranslate(rounding.x(), rounding.y());
548 mapping.mapPoints(fPositions, glyphRun.positions().data(), glyphRun.runSize());
549
550 int glyphsWithMaskCount = 0;
551 const SkPoint* positionCursor = glyphRun.positions().data();
552 const SkPoint* devicePositionCursor = fPositions;
553 for (auto glyphID : glyphRun.glyphsIDs()) {
554 SkPoint glyphPos = *positionCursor++;
555 SkPoint deviceGlyphPos = *devicePositionCursor++;
556 if (!SkScalarsAreFinite(deviceGlyphPos.x(), deviceGlyphPos.y())) {
557 continue;
558 }
559
560 const SkGlyph& glyph = strike->getGlyphMetrics(glyphID, deviceGlyphPos);
561 if (glyph.isEmpty()) {
562 continue;
563 }
564
565 if (SkStrikeCommon::GlyphTooBigForAtlas(glyph)) {
566 if (strike->decideCouldDrawFromPath(glyph)) {
567 fPaths.push_back({&glyph, deviceGlyphPos});
568 } else {
569 SkScalar largestDimension = std::max(glyph.fWidth, glyph.fHeight);
570 maxFallbackDimension = std::max(maxFallbackDimension, largestDimension);
571 fARGBGlyphsIDs.push_back(glyph.getGlyphID());
572 fARGBPositions.push_back(origin + glyphPos);
573 }
574 } else {
575 fGlyphPos[glyphsWithMaskCount++] = {&glyph, deviceGlyphPos};
576 }
577 }
578
579 if (process) {
580 if (glyphsWithMaskCount > 0) {
581 process->processDeviceMasks(
582 SkSpan<const SkGlyphPos>{fGlyphPos, SkTo<size_t>(glyphsWithMaskCount)},
583 strike.get());
584 }
585 if (!fPaths.empty()) {
586 process->processDevicePaths(SkSpan<const SkGlyphPos>{fPaths});
587 }
588
589 // fGlyphPos will be reused here.
590 if (!fARGBGlyphsIDs.empty()) {
591 this->processARGBFallback(maxFallbackDimension / viewMatrix.getMaxScale(),
592 runPaint, runFont, viewMatrix, process);
593 }
594 }
595
596 }
597
598 }
599 }
600 #endif // SK_SUPPORT_GPU
601
ensureBuffers(const SkGlyphRunList & glyphRunList)602 auto SkGlyphRunListPainter::ensureBuffers(const SkGlyphRunList& glyphRunList) -> ScopedBuffers {
603 size_t size = 0;
604 for (const SkGlyphRun& run : glyphRunList) {
605 size = std::max(run.runSize(), size);
606 }
607 return ScopedBuffers(this, size);
608 }
609
610 SkGlyphRunListPainter::ScopedBuffers
ensureBuffers(const SkGlyphRun & glyphRun)611 SkGlyphRunListPainter::ensureBuffers(const SkGlyphRun& glyphRun) {
612 return ScopedBuffers(this, glyphRun.runSize());
613 }
614
615 #if SK_SUPPORT_GPU
616 // -- GrTextContext --------------------------------------------------------------------------------
generate_filtered_color(const SkPaint & paint,const GrColorSpaceInfo & colorSpaceInfo)617 SkPMColor4f generate_filtered_color(const SkPaint& paint, const GrColorSpaceInfo& colorSpaceInfo) {
618 SkColor4f filteredColor = paint.getColor4f();
619 if (auto* xform = colorSpaceInfo.colorSpaceXformFromSRGB()) {
620 filteredColor = xform->apply(filteredColor);
621 }
622 if (paint.getColorFilter() != nullptr) {
623 filteredColor = paint.getColorFilter()->filterColor4f(filteredColor,
624 colorSpaceInfo.colorSpace());
625 }
626 return filteredColor.premul();
627 }
628
drawGlyphRunList(GrRecordingContext * context,GrTextTarget * target,const GrClip & clip,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkGlyphRunList & glyphRunList)629 void GrTextContext::drawGlyphRunList(
630 GrRecordingContext* context, GrTextTarget* target, const GrClip& clip,
631 const SkMatrix& viewMatrix, const SkSurfaceProps& props,
632 const SkGlyphRunList& glyphRunList) {
633 SkPoint origin = glyphRunList.origin();
634
635 // Get the first paint to use as the key paint.
636 const SkPaint& listPaint = glyphRunList.paint();
637
638 SkPMColor4f filteredColor = generate_filtered_color(listPaint, target->colorSpaceInfo());
639 GrColor color = generate_filtered_color(listPaint, target->colorSpaceInfo()).toBytes_RGBA();
640
641 // If we have been abandoned, then don't draw
642 if (context->priv().abandoned()) {
643 return;
644 }
645
646 SkMaskFilterBase::BlurRec blurRec;
647 // It might be worth caching these things, but its not clear at this time
648 // TODO for animated mask filters, this will fill up our cache. We need a safeguard here
649 const SkMaskFilter* mf = listPaint.getMaskFilter();
650 bool canCache = glyphRunList.canCache() && !(listPaint.getPathEffect() ||
651 (mf && !as_MFB(mf)->asABlur(&blurRec)));
652 SkScalerContextFlags scalerContextFlags = ComputeScalerContextFlags(target->colorSpaceInfo());
653
654 auto grStrikeCache = context->priv().getGrStrikeCache();
655 GrTextBlobCache* textBlobCache = context->priv().getTextBlobCache();
656
657 sk_sp<GrTextBlob> cacheBlob;
658 GrTextBlob::Key key;
659 if (canCache) {
660 bool hasLCD = glyphRunList.anyRunsLCD();
661
662 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry
663 SkPixelGeometry pixelGeometry = hasLCD ? props.pixelGeometry() :
664 kUnknown_SkPixelGeometry;
665
666 // TODO we want to figure out a way to be able to use the canonical color on LCD text,
667 // see the note on ComputeCanonicalColor above. We pick a dummy value for LCD text to
668 // ensure we always match the same key
669 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT :
670 ComputeCanonicalColor(listPaint, hasLCD);
671
672 key.fPixelGeometry = pixelGeometry;
673 key.fUniqueID = glyphRunList.uniqueID();
674 key.fStyle = listPaint.getStyle();
675 key.fHasBlur = SkToBool(mf);
676 key.fCanonicalColor = canonicalColor;
677 key.fScalerContextFlags = scalerContextFlags;
678 cacheBlob = textBlobCache->find(key);
679 }
680
681 if (cacheBlob) {
682 if (cacheBlob->mustRegenerate(listPaint, glyphRunList.anyRunsSubpixelPositioned(),
683 blurRec, viewMatrix, origin.x(),origin.y())) {
684 // We have to remake the blob because changes may invalidate our masks.
685 // TODO we could probably get away reuse most of the time if the pointer is unique,
686 // but we'd have to clear the subrun information
687 textBlobCache->remove(cacheBlob.get());
688 cacheBlob = textBlobCache->makeCachedBlob(
689 glyphRunList, key, blurRec, listPaint, color, grStrikeCache);
690 cacheBlob->generateFromGlyphRunList(
691 *context->priv().caps()->shaderCaps(), fOptions,
692 listPaint, scalerContextFlags, viewMatrix, props,
693 glyphRunList, target->glyphPainter());
694 } else {
695 textBlobCache->makeMRU(cacheBlob.get());
696
697 if (CACHE_SANITY_CHECK) {
698 sk_sp<GrTextBlob> sanityBlob(textBlobCache->makeBlob(
699 glyphRunList, color, grStrikeCache));
700 sanityBlob->setupKey(key, blurRec, listPaint);
701 cacheBlob->generateFromGlyphRunList(
702 *context->priv().caps()->shaderCaps(), fOptions,
703 listPaint, scalerContextFlags, viewMatrix, props, glyphRunList,
704 target->glyphPainter());
705 GrTextBlob::AssertEqual(*sanityBlob, *cacheBlob);
706 }
707 }
708 } else {
709 if (canCache) {
710 cacheBlob = textBlobCache->makeCachedBlob(
711 glyphRunList, key, blurRec, listPaint, color, grStrikeCache);
712 } else {
713 cacheBlob = textBlobCache->makeBlob(glyphRunList, color, grStrikeCache);
714 }
715 cacheBlob->generateFromGlyphRunList(
716 *context->priv().caps()->shaderCaps(), fOptions, listPaint,
717 scalerContextFlags, viewMatrix, props, glyphRunList,
718 target->glyphPainter());
719 }
720
721 cacheBlob->flush(target, props, fDistanceAdjustTable.get(), listPaint, filteredColor,
722 clip, viewMatrix, origin.x(), origin.y());
723 }
724
appendGlyph(GrGlyph * glyph,SkRect dstRect)725 void GrTextBlob::SubRun::appendGlyph(GrGlyph* glyph, SkRect dstRect) {
726
727 this->joinGlyphBounds(dstRect);
728
729 GrTextBlob* blob = fRun->fBlob;
730
731 bool hasW = this->hasWCoord();
732 // glyphs drawn in perspective must always have a w coord.
733 SkASSERT(hasW || !blob->fInitialViewMatrix.hasPerspective());
734 auto maskFormat = this->maskFormat();
735 size_t vertexStride = GetVertexStride(maskFormat, hasW);
736
737 intptr_t vertex = reinterpret_cast<intptr_t>(blob->fVertices + fVertexEndIndex);
738
739 // We always write the third position component used by SDFs. If it is unused it gets
740 // overwritten. Similarly, we always write the color and the blob will later overwrite it
741 // with texture coords if it is unused.
742 size_t colorOffset = hasW ? sizeof(SkPoint3) : sizeof(SkPoint);
743 // V0
744 *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fLeft, dstRect.fTop, 1.f};
745 *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
746 vertex += vertexStride;
747
748 // V1
749 *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fLeft, dstRect.fBottom, 1.f};
750 *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
751 vertex += vertexStride;
752
753 // V2
754 *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fRight, dstRect.fTop, 1.f};
755 *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
756 vertex += vertexStride;
757
758 // V3
759 *reinterpret_cast<SkPoint3*>(vertex) = {dstRect.fRight, dstRect.fBottom, 1.f};
760 *reinterpret_cast<GrColor*>(vertex + colorOffset) = fColor;
761
762 fVertexEndIndex += vertexStride * kVerticesPerGlyph;
763 blob->fGlyphs[fGlyphEndIndex++] = glyph;
764 }
765
switchSubRunIfNeededAndAppendGlyph(GrGlyph * glyph,const sk_sp<GrTextStrike> & strike,const SkRect & destRect,bool needsTransform)766 void GrTextBlob::Run::switchSubRunIfNeededAndAppendGlyph(GrGlyph* glyph,
767 const sk_sp<GrTextStrike>& strike,
768 const SkRect& destRect,
769 bool needsTransform) {
770 GrMaskFormat format = glyph->fMaskFormat;
771
772 SubRun* subRun = &fSubRunInfo.back();
773 if (fInitialized && subRun->maskFormat() != format) {
774 subRun = pushBackSubRun(fDescriptor, fColor);
775 subRun->setStrike(strike);
776 } else if (!fInitialized) {
777 subRun->setStrike(strike);
778 }
779
780 fInitialized = true;
781 subRun->setMaskFormat(format);
782 subRun->setNeedsTransform(needsTransform);
783 subRun->appendGlyph(glyph, destRect);
784 }
785
appendDeviceSpaceGlyph(const sk_sp<GrTextStrike> & strike,const SkGlyph & skGlyph,SkPoint origin)786 void GrTextBlob::Run::appendDeviceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
787 const SkGlyph& skGlyph, SkPoint origin) {
788 if (GrGlyph* glyph = strike->getGlyph(skGlyph)) {
789
790 SkRect glyphRect = glyph->destRect(origin);
791
792 if (!glyphRect.isEmpty()) {
793 this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, false);
794 }
795 }
796 }
797
appendSourceSpaceGlyph(const sk_sp<GrTextStrike> & strike,const SkGlyph & skGlyph,SkPoint origin,SkScalar textScale)798 void GrTextBlob::Run::appendSourceSpaceGlyph(const sk_sp<GrTextStrike>& strike,
799 const SkGlyph& skGlyph,
800 SkPoint origin,
801 SkScalar textScale) {
802 if (GrGlyph* glyph = strike->getGlyph(skGlyph)) {
803
804 SkRect glyphRect = glyph->destRect(origin, textScale);
805
806 if (!glyphRect.isEmpty()) {
807 this->switchSubRunIfNeededAndAppendGlyph(glyph, strike, glyphRect, true);
808 }
809 }
810 }
811
generateFromGlyphRunList(const GrShaderCaps & shaderCaps,const GrTextContext::Options & options,const SkPaint & paint,SkScalerContextFlags scalerContextFlags,const SkMatrix & viewMatrix,const SkSurfaceProps & props,const SkGlyphRunList & glyphRunList,SkGlyphRunListPainter * glyphPainter)812 void GrTextBlob::generateFromGlyphRunList(const GrShaderCaps& shaderCaps,
813 const GrTextContext::Options& options,
814 const SkPaint& paint,
815 SkScalerContextFlags scalerContextFlags,
816 const SkMatrix& viewMatrix,
817 const SkSurfaceProps& props,
818 const SkGlyphRunList& glyphRunList,
819 SkGlyphRunListPainter* glyphPainter) {
820 SkPoint origin = glyphRunList.origin();
821 const SkPaint& runPaint = glyphRunList.paint();
822 this->initReusableBlob(SkPaintPriv::ComputeLuminanceColor(runPaint), viewMatrix,
823 origin.x(), origin.y());
824
825 glyphPainter->processGlyphRunList(glyphRunList,
826 viewMatrix,
827 props,
828 shaderCaps.supportsDistanceFieldText(),
829 options,
830 this);
831 }
832
currentRun()833 GrTextBlob::Run* GrTextBlob::currentRun() {
834 return &fRuns[fRunCount - 1];
835 }
836
startRun(const SkGlyphRun & glyphRun,bool useSDFT)837 void GrTextBlob::startRun(const SkGlyphRun& glyphRun, bool useSDFT) {
838 if (useSDFT) {
839 this->setHasDistanceField();
840 }
841 Run* run = this->pushBackRun();
842 run->setRunFontAntiAlias(glyphRun.font().hasSomeAntiAliasing());
843 }
844
processDeviceMasks(SkSpan<const SkGlyphPos> masks,SkStrikeInterface * strike)845 void GrTextBlob::processDeviceMasks(SkSpan<const SkGlyphPos> masks,
846 SkStrikeInterface* strike) {
847 Run* run = this->currentRun();
848 this->setHasBitmap();
849 run->setupFont(strike->strikeSpec());
850 sk_sp<GrTextStrike> currStrike = fStrikeCache->getStrike(strike->getDescriptor());
851 for (const auto& mask : masks) {
852 SkPoint pt{SkScalarFloorToScalar(mask.position.fX),
853 SkScalarFloorToScalar(mask.position.fY)};
854 run->appendDeviceSpaceGlyph(currStrike, *mask.glyph, pt);
855 }
856 }
857
processSourcePaths(SkSpan<const SkGlyphPos> paths,SkStrikeInterface * strike,SkScalar cacheToSourceScale)858 void GrTextBlob::processSourcePaths(SkSpan<const SkGlyphPos> paths,
859 SkStrikeInterface* strike, SkScalar cacheToSourceScale) {
860 Run* run = this->currentRun();
861 this->setHasBitmap();
862 run->setupFont(strike->strikeSpec());
863 for (const auto& path : paths) {
864 if (const SkPath* glyphPath = path.glyph->path()) {
865 run->appendPathGlyph(*glyphPath, path.position, cacheToSourceScale,
866 false);
867 }
868 }
869 }
870
processDevicePaths(SkSpan<const SkGlyphPos> paths)871 void GrTextBlob::processDevicePaths(SkSpan<const SkGlyphPos> paths) {
872 Run* run = this->currentRun();
873 this->setHasBitmap();
874 for (const auto& path : paths) {
875 SkPoint pt{SkScalarFloorToScalar(path.position.fX),
876 SkScalarFloorToScalar(path.position.fY)};
877 // TODO: path should always be set. Remove when proven.
878 if (const SkPath* glyphPath = path.glyph->path()) {
879 run->appendPathGlyph(*glyphPath, pt, SK_Scalar1, true);
880 }
881 }
882 }
883
processSourceSDFT(SkSpan<const SkGlyphPos> masks,SkStrikeInterface * strike,const SkFont & runFont,SkScalar cacheToSourceScale,SkScalar minScale,SkScalar maxScale,bool hasWCoord)884 void GrTextBlob::processSourceSDFT(SkSpan<const SkGlyphPos> masks,
885 SkStrikeInterface* strike,
886 const SkFont& runFont,
887 SkScalar cacheToSourceScale,
888 SkScalar minScale,
889 SkScalar maxScale,
890 bool hasWCoord) {
891
892 Run* run = this->currentRun();
893 run->setSubRunHasDistanceFields(
894 runFont.getEdging() == SkFont::Edging::kSubpixelAntiAlias,
895 runFont.hasSomeAntiAliasing(),
896 hasWCoord);
897 this->setMinAndMaxScale(minScale, maxScale);
898 run->setupFont(strike->strikeSpec());
899 sk_sp<GrTextStrike> currStrike = fStrikeCache->getStrike(strike->getDescriptor());
900 for (const auto& mask : masks) {
901 run->appendSourceSpaceGlyph(
902 currStrike, *mask.glyph, mask.position, cacheToSourceScale);
903 }
904 }
905
processSourceFallback(SkSpan<const SkGlyphPos> masks,SkStrikeInterface * strike,SkScalar cacheToSourceScale,bool hasW)906 void GrTextBlob::processSourceFallback(SkSpan<const SkGlyphPos> masks,
907 SkStrikeInterface* strike, SkScalar cacheToSourceScale,
908 bool hasW) {
909 Run* run = this->currentRun();
910
911 auto subRun = run->initARGBFallback();
912 sk_sp<GrTextStrike> grStrike =
913 fStrikeCache->getStrike(strike->getDescriptor());
914 subRun->setStrike(grStrike);
915 subRun->setHasWCoord(hasW);
916
917 this->setHasBitmap();
918 run->setupFont(strike->strikeSpec());
919 for (const auto& mask : masks) {
920 run->appendSourceSpaceGlyph
921 (grStrike, *mask.glyph, mask.position, cacheToSourceScale);
922 }
923 }
924
processDeviceFallback(SkSpan<const SkGlyphPos> masks,SkStrikeInterface * strike)925 void GrTextBlob::processDeviceFallback(SkSpan<const SkGlyphPos> masks,
926 SkStrikeInterface* strike) {
927 Run* run = this->currentRun();
928 this->setHasBitmap();
929 sk_sp<GrTextStrike> grStrike = fStrikeCache->getStrike(strike->getDescriptor());
930 auto subRun = run->initARGBFallback();
931 run->setupFont(strike->strikeSpec());
932 subRun->setStrike(grStrike);
933 for (const auto& mask : masks) {
934 run->appendDeviceSpaceGlyph(grStrike, *mask.glyph, mask.position);
935 }
936 }
937
938 #if GR_TEST_UTILS
939
940 #include "GrRenderTargetContext.h"
941 #include "GrRecordingContextPriv.h"
942
createOp_TestingOnly(GrRecordingContext * context,GrTextContext * textContext,GrRenderTargetContext * rtc,const SkPaint & skPaint,const SkFont & font,const SkMatrix & viewMatrix,const char * text,int x,int y)943 std::unique_ptr<GrDrawOp> GrTextContext::createOp_TestingOnly(GrRecordingContext* context,
944 GrTextContext* textContext,
945 GrRenderTargetContext* rtc,
946 const SkPaint& skPaint,
947 const SkFont& font,
948 const SkMatrix& viewMatrix,
949 const char* text,
950 int x,
951 int y) {
952 auto direct = context->priv().asDirectContext();
953 if (!direct) {
954 return nullptr;
955 }
956
957 auto strikeCache = direct->priv().getGrStrikeCache();
958
959 static SkSurfaceProps surfaceProps(SkSurfaceProps::kLegacyFontHost_InitType);
960
961 size_t textLen = (int)strlen(text);
962
963 SkPMColor4f filteredColor = generate_filtered_color(skPaint, rtc->colorSpaceInfo());
964 GrColor color = filteredColor.toBytes_RGBA();
965
966 auto origin = SkPoint::Make(x, y);
967 SkGlyphRunBuilder builder;
968 builder.drawTextUTF8(skPaint, font, text, textLen, origin);
969
970 auto glyphRunList = builder.useGlyphRunList();
971 sk_sp<GrTextBlob> blob;
972 if (!glyphRunList.empty()) {
973 blob = direct->priv().getTextBlobCache()->makeBlob(glyphRunList, color, strikeCache);
974 // Use the text and textLen below, because we don't want to mess with the paint.
975 SkScalerContextFlags scalerContextFlags =
976 ComputeScalerContextFlags(rtc->colorSpaceInfo());
977 blob->generateFromGlyphRunList(
978 *context->priv().caps()->shaderCaps(), textContext->fOptions,
979 skPaint, scalerContextFlags, viewMatrix, surfaceProps,
980 glyphRunList, rtc->textTarget()->glyphPainter());
981 }
982
983 return blob->test_makeOp(textLen, 0, 0, viewMatrix, x, y, skPaint, filteredColor, surfaceProps,
984 textContext->dfAdjustTable(), rtc->textTarget());
985 }
986
987 #endif // GR_TEST_UTILS
988 #endif // SK_SUPPORT_GPU
989
ScopedBuffers(SkGlyphRunListPainter * painter,int size)990 SkGlyphRunListPainter::ScopedBuffers::ScopedBuffers(SkGlyphRunListPainter* painter, int size)
991 : fPainter{painter} {
992 SkASSERT(size >= 0);
993 if (fPainter->fMaxRunSize < size) {
994 fPainter->fMaxRunSize = size;
995
996 fPainter->fPositions.reset(size);
997 fPainter->fGlyphPos.reset(size);
998 }
999 }
1000
~ScopedBuffers()1001 SkGlyphRunListPainter::ScopedBuffers::~ScopedBuffers() {
1002 fPainter->fPaths.clear();
1003 fPainter->fARGBGlyphsIDs.clear();
1004 fPainter->fARGBPositions.clear();
1005
1006 if (fPainter->fMaxRunSize > 200) {
1007 fPainter->fMaxRunSize = 0;
1008 fPainter->fPositions.reset();
1009 fPainter->fGlyphPos.reset();
1010 fPainter->fPaths.shrink_to_fit();
1011 fPainter->fARGBGlyphsIDs.shrink_to_fit();
1012 fPainter->fARGBPositions.shrink_to_fit();
1013 }
1014 }
1015