OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright 2015 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 "GrAtlasTextContext.h" | |
8 | |
9 #include "GrDrawContext.h" | |
10 #include "GrDrawTarget.h" | |
11 #include "GrFontScaler.h" | |
12 #include "GrStrokeInfo.h" | |
13 #include "GrTextBlobCache.h" | |
14 #include "GrTexturePriv.h" | |
15 #include "GrTextUtils.h" | |
16 #include "GrVertexBuffer.h" | |
17 | |
18 #include "SkAutoKern.h" | |
19 #include "SkColorPriv.h" | |
20 #include "SkColorFilter.h" | |
21 #include "SkDistanceFieldGen.h" | |
22 #include "SkDraw.h" | |
23 #include "SkDrawFilter.h" | |
24 #include "SkDrawProcs.h" | |
25 #include "SkFindAndPlaceGlyph.h" | |
26 #include "SkGlyphCache.h" | |
27 #include "SkGpuDevice.h" | |
28 #include "SkGrPriv.h" | |
29 #include "SkPath.h" | |
30 #include "SkRTConf.h" | |
31 #include "SkStrokeRec.h" | |
32 #include "SkTextBlob.h" | |
33 #include "SkTextMapStateProc.h" | |
34 | |
35 #include "batches/GrAtlasTextBatch.h" | |
36 | |
37 namespace { | |
38 static const int kMinDFFontSize = 18; | |
39 static const int kSmallDFFontSize = 32; | |
40 static const int kSmallDFFontLimit = 32; | |
41 static const int kMediumDFFontSize = 72; | |
42 static const int kMediumDFFontLimit = 72; | |
43 static const int kLargeDFFontSize = 162; | |
44 #ifdef SK_BUILD_FOR_ANDROID | |
45 static const int kLargeDFFontLimit = 384; | |
46 #else | |
47 static const int kLargeDFFontLimit = 2 * kLargeDFFontSize; | |
48 #endif | |
49 }; | |
50 | |
51 GrAtlasTextContext::GrAtlasTextContext(GrContext* context, const SkSurfaceProps&
surfaceProps) | |
52 : INHERITED(context, surfaceProps) | |
53 , fDistanceAdjustTable(new GrDistanceFieldAdjustTable) { | |
54 // We overallocate vertices in our textblobs based on the assumption that A8
has the greatest | |
55 // vertexStride | |
56 static_assert(GrAtlasTextBlob::kGrayTextVASize >= GrAtlasTextBlob::kColorTex
tVASize && | |
57 GrAtlasTextBlob::kGrayTextVASize >= GrAtlasTextBlob::kLCDTextV
ASize, | |
58 "vertex_attribute_changed"); | |
59 fCurrStrike = nullptr; | |
60 fCache = context->getTextBlobCache(); | |
61 } | |
62 | |
63 | |
64 GrAtlasTextContext* GrAtlasTextContext::Create(GrContext* context, | |
65 const SkSurfaceProps& surfaceProp
s) { | |
66 return new GrAtlasTextContext(context, surfaceProps); | |
67 } | |
68 | |
69 bool GrAtlasTextContext::canDraw(const SkPaint& skPaint, const SkMatrix& viewMat
rix) { | |
70 return this->canDrawAsDistanceFields(skPaint, viewMatrix) || | |
71 !SkDraw::ShouldDrawTextAsPaths(skPaint, viewMatrix); | |
72 } | |
73 | |
74 GrColor GrAtlasTextContext::ComputeCanonicalColor(const SkPaint& paint, bool lcd
) { | |
75 GrColor canonicalColor = paint.computeLuminanceColor(); | |
76 if (lcd) { | |
77 // This is the correct computation, but there are tons of cases where LC
D can be overridden. | |
78 // For now we just regenerate if any run in a textblob has LCD. | |
79 // TODO figure out where all of these overrides are and see if we can in
corporate that logic | |
80 // at a higher level *OR* use sRGB | |
81 SkASSERT(false); | |
82 //canonicalColor = SkMaskGamma::CanonicalColor(canonicalColor); | |
83 } else { | |
84 // A8, though can have mixed BMP text but it shouldn't matter because BM
P text won't have | |
85 // gamma corrected masks anyways, nor color | |
86 U8CPU lum = SkComputeLuminance(SkColorGetR(canonicalColor), | |
87 SkColorGetG(canonicalColor), | |
88 SkColorGetB(canonicalColor)); | |
89 // reduce to our finite number of bits | |
90 canonicalColor = SkMaskGamma::CanonicalColor(SkColorSetRGB(lum, lum, lum
)); | |
91 } | |
92 return canonicalColor; | |
93 } | |
94 | |
95 // TODO if this function ever shows up in profiling, then we can compute this va
lue when the | |
96 // textblob is being built and cache it. However, for the time being textblobs
mostly only have 1 | |
97 // run so this is not a big deal to compute here. | |
98 bool GrAtlasTextContext::HasLCD(const SkTextBlob* blob) { | |
99 SkTextBlobRunIterator it(blob); | |
100 for (; !it.done(); it.next()) { | |
101 if (it.isLCD()) { | |
102 return true; | |
103 } | |
104 } | |
105 return false; | |
106 } | |
107 | |
108 inline SkGlyphCache* GrAtlasTextContext::setupCache(GrAtlasTextBlob::Run* run, | |
109 const SkPaint& skPaint, | |
110 const SkMatrix* viewMatrix, | |
111 bool noGamma) { | |
112 skPaint.getScalerContextDescriptor(&run->fDescriptor, fSurfaceProps, viewMat
rix, noGamma); | |
113 run->fTypeface.reset(SkSafeRef(skPaint.getTypeface())); | |
114 return SkGlyphCache::DetachCache(run->fTypeface, run->fDescriptor.getDesc())
; | |
115 } | |
116 | |
117 void GrAtlasTextContext::drawTextBlob(GrDrawContext* dc, | |
118 const GrClip& clip, const SkPaint& skPaint
, | |
119 const SkMatrix& viewMatrix, const SkTextBl
ob* blob, | |
120 SkScalar x, SkScalar y, | |
121 SkDrawFilter* drawFilter, const SkIRect& c
lipBounds) { | |
122 // If we have been abandoned, then don't draw | |
123 if (fContext->abandoned()) { | |
124 return; | |
125 } | |
126 | |
127 SkAutoTUnref<GrAtlasTextBlob> cacheBlob; | |
128 SkMaskFilter::BlurRec blurRec; | |
129 GrAtlasTextBlob::Key key; | |
130 // It might be worth caching these things, but its not clear at this time | |
131 // TODO for animated mask filters, this will fill up our cache. We need a s
afeguard here | |
132 const SkMaskFilter* mf = skPaint.getMaskFilter(); | |
133 bool canCache = !(skPaint.getPathEffect() || | |
134 (mf && !mf->asABlur(&blurRec)) || | |
135 drawFilter); | |
136 | |
137 if (canCache) { | |
138 bool hasLCD = HasLCD(blob); | |
139 | |
140 // We canonicalize all non-lcd draws to use kUnknown_SkPixelGeometry | |
141 SkPixelGeometry pixelGeometry = hasLCD ? fSurfaceProps.pixelGeometry() : | |
142 kUnknown_SkPixelGeometry; | |
143 | |
144 // TODO we want to figure out a way to be able to use the canonical colo
r on LCD text, | |
145 // see the note on ComputeCanonicalColor above. We pick a dummy value f
or LCD text to | |
146 // ensure we always match the same key | |
147 GrColor canonicalColor = hasLCD ? SK_ColorTRANSPARENT : | |
148 ComputeCanonicalColor(skPaint, hasLCD)
; | |
149 | |
150 key.fPixelGeometry = pixelGeometry; | |
151 key.fUniqueID = blob->uniqueID(); | |
152 key.fStyle = skPaint.getStyle(); | |
153 key.fHasBlur = SkToBool(mf); | |
154 key.fCanonicalColor = canonicalColor; | |
155 cacheBlob.reset(SkSafeRef(fCache->find(key))); | |
156 } | |
157 | |
158 SkScalar transX = 0.f; | |
159 SkScalar transY = 0.f; | |
160 | |
161 // Though for the time being runs in the textblob can override the paint, th
ey only touch font | |
162 // info. | |
163 GrPaint grPaint; | |
164 if (!SkPaintToGrPaint(fContext, skPaint, viewMatrix, &grPaint)) { | |
165 return; | |
166 } | |
167 | |
168 if (cacheBlob) { | |
169 if (cacheBlob->mustRegenerate(&transX, &transY, skPaint, grPaint.getColo
r(), blurRec, | |
170 viewMatrix, x, y)) { | |
171 // We have to remake the blob because changes may invalidate our mas
ks. | |
172 // TODO we could probably get away reuse most of the time if the poi
nter is unique, | |
173 // but we'd have to clear the subrun information | |
174 fCache->remove(cacheBlob); | |
175 cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, s
kPaint, | |
176 GrAtlasTextBlob::kGra
yTextVASize))); | |
177 this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), vie
wMatrix, | |
178 blob, x, y, drawFilter, clip); | |
179 } else { | |
180 fCache->makeMRU(cacheBlob); | |
181 #ifdef CACHE_SANITY_CHECK | |
182 { | |
183 int glyphCount = 0; | |
184 int runCount = 0; | |
185 GrTextBlobCache::BlobGlyphCount(&glyphCount, &runCount, blob); | |
186 SkAutoTUnref<GrAtlasTextBlob> sanityBlob(fCache->createBlob(glyp
hCount, runCount, | |
187 kGra
yTextVASize)); | |
188 GrTextBlobCache::SetupCacheBlobKey(sanityBlob, key, blurRec, skP
aint); | |
189 this->regenerateTextBlob(sanityBlob, skPaint, grPaint.getColor()
, viewMatrix, | |
190 blob, x, y, drawFilter, clip); | |
191 GrAtlasTextBlob::AssertEqual(*sanityBlob, *cacheBlob); | |
192 } | |
193 | |
194 #endif | |
195 } | |
196 } else { | |
197 if (canCache) { | |
198 cacheBlob.reset(SkRef(fCache->createCachedBlob(blob, key, blurRec, s
kPaint, | |
199 GrAtlasTextBlob::kGra
yTextVASize))); | |
200 } else { | |
201 cacheBlob.reset(fCache->createBlob(blob, GrAtlasTextBlob::kGrayTextV
ASize)); | |
202 } | |
203 this->regenerateTextBlob(cacheBlob, skPaint, grPaint.getColor(), viewMat
rix, | |
204 blob, x, y, drawFilter, clip); | |
205 } | |
206 | |
207 cacheBlob->flushCached(fContext, dc, blob, fSurfaceProps, fDistanceAdjustTab
le, skPaint, | |
208 grPaint, drawFilter, clip, viewMatrix, clipBounds, x,
y, transX, transY); | |
209 } | |
210 | |
211 inline bool GrAtlasTextContext::canDrawAsDistanceFields(const SkPaint& skPaint, | |
212 const SkMatrix& viewMatr
ix) { | |
213 // TODO: support perspective (need getMaxScale replacement) | |
214 if (viewMatrix.hasPerspective()) { | |
215 return false; | |
216 } | |
217 | |
218 SkScalar maxScale = viewMatrix.getMaxScale(); | |
219 SkScalar scaledTextSize = maxScale*skPaint.getTextSize(); | |
220 // Hinted text looks far better at small resolutions | |
221 // Scaling up beyond 2x yields undesireable artifacts | |
222 if (scaledTextSize < kMinDFFontSize || scaledTextSize > kLargeDFFontLimit) { | |
223 return false; | |
224 } | |
225 | |
226 bool useDFT = fSurfaceProps.isUseDeviceIndependentFonts(); | |
227 #if SK_FORCE_DISTANCE_FIELD_TEXT | |
228 useDFT = true; | |
229 #endif | |
230 | |
231 if (!useDFT && scaledTextSize < kLargeDFFontSize) { | |
232 return false; | |
233 } | |
234 | |
235 // rasterizers and mask filters modify alpha, which doesn't | |
236 // translate well to distance | |
237 if (skPaint.getRasterizer() || skPaint.getMaskFilter() || | |
238 !fContext->caps()->shaderCaps()->shaderDerivativeSupport()) { | |
239 return false; | |
240 } | |
241 | |
242 // TODO: add some stroking support | |
243 if (skPaint.getStyle() != SkPaint::kFill_Style) { | |
244 return false; | |
245 } | |
246 | |
247 return true; | |
248 } | |
249 | |
250 void GrAtlasTextContext::regenerateTextBlob(GrAtlasTextBlob* cacheBlob, | |
251 const SkPaint& skPaint, GrColor colo
r, | |
252 const SkMatrix& viewMatrix, | |
253 const SkTextBlob* blob, SkScalar x,
SkScalar y, | |
254 SkDrawFilter* drawFilter, | |
255 const GrClip& clip) { | |
256 // The color here is the GrPaint color, and it is used to determine whether
we | |
257 // have to regenerate LCD text blobs. | |
258 // We use this color vs the SkPaint color because it has the colorfilter app
lied. | |
259 cacheBlob->fPaintColor = color; | |
260 cacheBlob->fViewMatrix = viewMatrix; | |
261 cacheBlob->fX = x; | |
262 cacheBlob->fY = y; | |
263 | |
264 // Regenerate textblob | |
265 SkPaint runPaint = skPaint; | |
266 SkTextBlobRunIterator it(blob); | |
267 for (int run = 0; !it.done(); it.next(), run++) { | |
268 int glyphCount = it.glyphCount(); | |
269 size_t textLen = glyphCount * sizeof(uint16_t); | |
270 const SkPoint& offset = it.offset(); | |
271 // applyFontToPaint() always overwrites the exact same attributes, | |
272 // so it is safe to not re-seed the paint for this reason. | |
273 it.applyFontToPaint(&runPaint); | |
274 | |
275 if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Typ
e)) { | |
276 // A false return from filter() means we should abort the current dr
aw. | |
277 runPaint = skPaint; | |
278 continue; | |
279 } | |
280 | |
281 runPaint.setFlags(FilterTextFlags(fSurfaceProps, runPaint)); | |
282 | |
283 cacheBlob->push_back_run(run); | |
284 | |
285 if (this->canDrawAsDistanceFields(runPaint, viewMatrix)) { | |
286 cacheBlob->setHasDistanceField(); | |
287 SkPaint dfPaint = runPaint; | |
288 SkScalar textRatio; | |
289 this->initDistanceFieldPaint(cacheBlob, &dfPaint, &textRatio, viewMa
trix); | |
290 Run& runIdx = cacheBlob->fRuns[run]; | |
291 PerSubRunInfo& subRun = runIdx.fSubRunInfo.back(); | |
292 subRun.setUseLCDText(runPaint.isLCDRenderText()); | |
293 subRun.setDrawAsDistanceFields(); | |
294 | |
295 SkTDArray<char> fallbackTxt; | |
296 SkTDArray<SkScalar> fallbackPos; | |
297 SkPoint dfOffset; | |
298 int scalarsPerPosition = 2; | |
299 switch (it.positioning()) { | |
300 case SkTextBlob::kDefault_Positioning: { | |
301 this->internalDrawDFText(cacheBlob, run, dfPaint, color, vie
wMatrix, | |
302 (const char *)it.glyphs(), textLen, | |
303 x + offset.x(), y + offset.y(), tex
tRatio, | |
304 &fallbackTxt, &fallbackPos, &dfOffs
et, runPaint); | |
305 break; | |
306 } | |
307 case SkTextBlob::kHorizontal_Positioning: { | |
308 scalarsPerPosition = 1; | |
309 dfOffset = SkPoint::Make(x, y + offset.y()); | |
310 this->internalDrawDFPosText(cacheBlob, run, dfPaint, color,
viewMatrix, | |
311 (const char*)it.glyphs(), textLe
n, it.pos(), | |
312 scalarsPerPosition, dfOffset, te
xtRatio, | |
313 &fallbackTxt, &fallbackPos); | |
314 break; | |
315 } | |
316 case SkTextBlob::kFull_Positioning: { | |
317 dfOffset = SkPoint::Make(x, y); | |
318 this->internalDrawDFPosText(cacheBlob, run, dfPaint, color,
viewMatrix, | |
319 (const char*)it.glyphs(), textLe
n, it.pos(), | |
320 scalarsPerPosition, dfOffset, te
xtRatio, | |
321 &fallbackTxt, &fallbackPos); | |
322 break; | |
323 } | |
324 } | |
325 if (fallbackTxt.count()) { | |
326 this->fallbackDrawPosText(cacheBlob, run, clip, color, runPaint,
viewMatrix, | |
327 fallbackTxt, fallbackPos, scalarsPerPo
sition, dfOffset); | |
328 } | |
329 } else if (SkDraw::ShouldDrawTextAsPaths(runPaint, viewMatrix)) { | |
330 cacheBlob->fRuns[run].fDrawAsPaths = true; | |
331 } else { | |
332 cacheBlob->setHasBitmap(); | |
333 SkGlyphCache* cache = this->setupCache(&cacheBlob->fRuns[run], runPa
int, &viewMatrix, | |
334 false); | |
335 switch (it.positioning()) { | |
336 case SkTextBlob::kDefault_Positioning: | |
337 GrTextUtils::DrawBmpText(cacheBlob, run, fContext->getBatchF
ontCache(), | |
338 cache, runPaint, color, viewMatrix, | |
339 (const char *)it.glyphs(), textLen, | |
340 x + offset.x(), y + offset.y()); | |
341 break; | |
342 case SkTextBlob::kHorizontal_Positioning: | |
343 GrTextUtils::DrawBmpPosText(cacheBlob, run, fContext->getBat
chFontCache(), | |
344 cache, runPaint, color, viewMatr
ix, | |
345 (const char*)it.glyphs(), textLe
n, it.pos(), 1, | |
346 SkPoint::Make(x, y + offset.y())
); | |
347 break; | |
348 case SkTextBlob::kFull_Positioning: | |
349 GrTextUtils::DrawBmpPosText(cacheBlob, run, fContext->getBat
chFontCache(), | |
350 cache, runPaint, color, viewMatr
ix, | |
351 (const char*)it.glyphs(), textLe
n, it.pos(), 2, | |
352 SkPoint::Make(x, y)); | |
353 break; | |
354 } | |
355 SkGlyphCache::AttachCache(cache); | |
356 } | |
357 | |
358 if (drawFilter) { | |
359 // A draw filter may change the paint arbitrarily, so we must re-see
d in this case. | |
360 runPaint = skPaint; | |
361 } | |
362 } | |
363 } | |
364 | |
365 inline void GrAtlasTextContext::initDistanceFieldPaint(GrAtlasTextBlob* blob, | |
366 SkPaint* skPaint, | |
367 SkScalar* textRatio, | |
368 const SkMatrix& viewMatri
x) { | |
369 // getMaxScale doesn't support perspective, so neither do we at the moment | |
370 SkASSERT(!viewMatrix.hasPerspective()); | |
371 SkScalar maxScale = viewMatrix.getMaxScale(); | |
372 SkScalar textSize = skPaint->getTextSize(); | |
373 SkScalar scaledTextSize = textSize; | |
374 // if we have non-unity scale, we need to choose our base text size | |
375 // based on the SkPaint's text size multiplied by the max scale factor | |
376 // TODO: do we need to do this if we're scaling down (i.e. maxScale < 1)? | |
377 if (maxScale > 0 && !SkScalarNearlyEqual(maxScale, SK_Scalar1)) { | |
378 scaledTextSize *= maxScale; | |
379 } | |
380 | |
381 // We have three sizes of distance field text, and within each size 'bucket'
there is a floor | |
382 // and ceiling. A scale outside of this range would require regenerating th
e distance fields | |
383 SkScalar dfMaskScaleFloor; | |
384 SkScalar dfMaskScaleCeil; | |
385 if (scaledTextSize <= kSmallDFFontLimit) { | |
386 dfMaskScaleFloor = kMinDFFontSize; | |
387 dfMaskScaleCeil = kSmallDFFontLimit; | |
388 *textRatio = textSize / kSmallDFFontSize; | |
389 skPaint->setTextSize(SkIntToScalar(kSmallDFFontSize)); | |
390 } else if (scaledTextSize <= kMediumDFFontLimit) { | |
391 dfMaskScaleFloor = kSmallDFFontLimit; | |
392 dfMaskScaleCeil = kMediumDFFontLimit; | |
393 *textRatio = textSize / kMediumDFFontSize; | |
394 skPaint->setTextSize(SkIntToScalar(kMediumDFFontSize)); | |
395 } else { | |
396 dfMaskScaleFloor = kMediumDFFontLimit; | |
397 dfMaskScaleCeil = kLargeDFFontLimit; | |
398 *textRatio = textSize / kLargeDFFontSize; | |
399 skPaint->setTextSize(SkIntToScalar(kLargeDFFontSize)); | |
400 } | |
401 | |
402 // Because there can be multiple runs in the blob, we want the overall maxMi
nScale, and | |
403 // minMaxScale to make regeneration decisions. Specifically, we want the ma
ximum minimum scale | |
404 // we can tolerate before we'd drop to a lower mip size, and the minimum max
imum scale we can | |
405 // tolerate before we'd have to move to a large mip size. When we actually
test these values | |
406 // we look at the delta in scale between the new viewmatrix and the old view
matrix, and test | |
407 // against these values to decide if we can reuse or not(ie, will a given sc
ale change our mip | |
408 // level) | |
409 SkASSERT(dfMaskScaleFloor <= scaledTextSize && scaledTextSize <= dfMaskScale
Ceil); | |
410 blob->fMaxMinScale = SkMaxScalar(dfMaskScaleFloor / scaledTextSize, blob->fM
axMinScale); | |
411 blob->fMinMaxScale = SkMinScalar(dfMaskScaleCeil / scaledTextSize, blob->fMi
nMaxScale); | |
412 | |
413 skPaint->setLCDRenderText(false); | |
414 skPaint->setAutohinted(false); | |
415 skPaint->setHinting(SkPaint::kNormal_Hinting); | |
416 skPaint->setSubpixelText(true); | |
417 } | |
418 | |
419 inline void GrAtlasTextContext::fallbackDrawPosText(GrAtlasTextBlob* blob, | |
420 int runIndex, | |
421 const GrClip& clip, | |
422 GrColor color, | |
423 const SkPaint& skPaint, | |
424 const SkMatrix& viewMatrix, | |
425 const SkTDArray<char>& fallb
ackTxt, | |
426 const SkTDArray<SkScalar>& f
allbackPos, | |
427 int scalarsPerPosition, | |
428 const SkPoint& offset) { | |
429 SkASSERT(fallbackTxt.count()); | |
430 blob->setHasBitmap(); | |
431 Run& run = blob->fRuns[runIndex]; | |
432 // Push back a new subrun to fill and set the override descriptor | |
433 run.push_back(); | |
434 run.fOverrideDescriptor.reset(new SkAutoDescriptor); | |
435 skPaint.getScalerContextDescriptor(run.fOverrideDescriptor, | |
436 fSurfaceProps, &viewMatrix, false); | |
437 SkGlyphCache* cache = SkGlyphCache::DetachCache(run.fTypeface, | |
438 run.fOverrideDescriptor->get
Desc()); | |
439 GrTextUtils::DrawBmpPosText(blob, runIndex, fContext->getBatchFontCache(), c
ache, skPaint, | |
440 color, viewMatrix, fallbackTxt.begin(), fallback
Txt.count(), | |
441 fallbackPos.begin(), scalarsPerPosition, offset)
; | |
442 SkGlyphCache::AttachCache(cache); | |
443 } | |
444 | |
445 inline GrAtlasTextBlob* | |
446 GrAtlasTextContext::setupDFBlob(int glyphCount, const SkPaint& origPaint, | |
447 const SkMatrix& viewMatrix, SkPaint* dfPaint, | |
448 SkScalar* textRatio) { | |
449 GrAtlasTextBlob* blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBlob::k
GrayTextVASize); | |
450 | |
451 *dfPaint = origPaint; | |
452 this->initDistanceFieldPaint(blob, dfPaint, textRatio, viewMatrix); | |
453 blob->fViewMatrix = viewMatrix; | |
454 Run& run = blob->fRuns[0]; | |
455 PerSubRunInfo& subRun = run.fSubRunInfo.back(); | |
456 subRun.setUseLCDText(origPaint.isLCDRenderText()); | |
457 subRun.setDrawAsDistanceFields(); | |
458 | |
459 return blob; | |
460 } | |
461 | |
462 inline GrAtlasTextBlob* | |
463 GrAtlasTextContext::createDrawTextBlob(const GrClip& clip, | |
464 const GrPaint& paint, const SkPaint& skPa
int, | |
465 const SkMatrix& viewMatrix, | |
466 const char text[], size_t byteLength, | |
467 SkScalar x, SkScalar y, const SkIRect& re
gionClipBounds) { | |
468 int glyphCount = skPaint.countText(text, byteLength); | |
469 | |
470 GrAtlasTextBlob* blob; | |
471 if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) { | |
472 SkPaint dfPaint; | |
473 SkScalar textRatio; | |
474 blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &dfPaint, &tex
tRatio); | |
475 | |
476 SkTDArray<char> fallbackTxt; | |
477 SkTDArray<SkScalar> fallbackPos; | |
478 SkPoint offset; | |
479 this->internalDrawDFText(blob, 0, dfPaint, paint.getColor(), viewMatrix,
text, | |
480 byteLength, x, y, textRatio, &fallbackTxt, &fal
lbackPos, | |
481 &offset, skPaint); | |
482 if (fallbackTxt.count()) { | |
483 this->fallbackDrawPosText(blob, 0, clip, paint.getColor(), skPaint,
viewMatrix, | |
484 fallbackTxt, fallbackPos, 2, offset); | |
485 } | |
486 } else { | |
487 blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBlob::kGrayTextVASiz
e); | |
488 blob->fViewMatrix = viewMatrix; | |
489 | |
490 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMa
trix, false); | |
491 GrTextUtils::DrawBmpText(blob, 0, fContext->getBatchFontCache(), cache,
skPaint, | |
492 paint.getColor(), viewMatrix, text, byteLength,
x, y); | |
493 SkGlyphCache::AttachCache(cache); | |
494 } | |
495 return blob; | |
496 } | |
497 | |
498 inline GrAtlasTextBlob* | |
499 GrAtlasTextContext::createDrawPosTextBlob(const GrClip& clip, | |
500 const GrPaint& paint, const SkPaint& s
kPaint, | |
501 const SkMatrix& viewMatrix, | |
502 const char text[], size_t byteLength, | |
503 const SkScalar pos[], int scalarsPerPo
sition, | |
504 const SkPoint& offset, const SkIRect&
regionClipBounds) { | |
505 int glyphCount = skPaint.countText(text, byteLength); | |
506 | |
507 GrAtlasTextBlob* blob; | |
508 if (this->canDrawAsDistanceFields(skPaint, viewMatrix)) { | |
509 SkPaint dfPaint; | |
510 SkScalar textRatio; | |
511 blob = this->setupDFBlob(glyphCount, skPaint, viewMatrix, &dfPaint, &tex
tRatio); | |
512 | |
513 SkTDArray<char> fallbackTxt; | |
514 SkTDArray<SkScalar> fallbackPos; | |
515 this->internalDrawDFPosText(blob, 0, dfPaint, paint.getColor(), viewMatr
ix, text, | |
516 byteLength, pos, scalarsPerPosition, offset, | |
517 textRatio, &fallbackTxt, &fallbackPos); | |
518 if (fallbackTxt.count()) { | |
519 this->fallbackDrawPosText(blob, 0, clip, paint.getColor(), skPaint,
viewMatrix, | |
520 fallbackTxt, fallbackPos, scalarsPerPositi
on, offset); | |
521 } | |
522 } else { | |
523 blob = fCache->createBlob(glyphCount, 1, GrAtlasTextBlob::kGrayTextVASiz
e); | |
524 blob->fViewMatrix = viewMatrix; | |
525 SkGlyphCache* cache = this->setupCache(&blob->fRuns[0], skPaint, &viewMa
trix, false); | |
526 GrTextUtils::DrawBmpPosText(blob, 0, fContext->getBatchFontCache(), cach
e, skPaint, | |
527 paint.getColor(), viewMatrix, text, | |
528 byteLength, pos, scalarsPerPosition, offset)
; | |
529 SkGlyphCache::AttachCache(cache); | |
530 } | |
531 return blob; | |
532 } | |
533 | |
534 void GrAtlasTextContext::onDrawText(GrDrawContext* dc, | |
535 const GrClip& clip, | |
536 const GrPaint& paint, const SkPaint& skPaint
, | |
537 const SkMatrix& viewMatrix, | |
538 const char text[], size_t byteLength, | |
539 SkScalar x, SkScalar y, const SkIRect& regio
nClipBounds) { | |
540 SkAutoTUnref<GrAtlasTextBlob> blob( | |
541 this->createDrawTextBlob(clip, paint, skPaint, viewMatrix, | |
542 text, byteLength, x, y, regionClipBounds)); | |
543 blob->flushThrowaway(fContext, dc, fSurfaceProps, fDistanceAdjustTable, skPa
int, paint, | |
544 clip, regionClipBounds); | |
545 } | |
546 | |
547 void GrAtlasTextContext::onDrawPosText(GrDrawContext* dc, | |
548 const GrClip& clip, | |
549 const GrPaint& paint, const SkPaint& skPa
int, | |
550 const SkMatrix& viewMatrix, | |
551 const char text[], size_t byteLength, | |
552 const SkScalar pos[], int scalarsPerPosit
ion, | |
553 const SkPoint& offset, const SkIRect& reg
ionClipBounds) { | |
554 SkAutoTUnref<GrAtlasTextBlob> blob( | |
555 this->createDrawPosTextBlob(clip, paint, skPaint, viewMatrix, | |
556 text, byteLength, | |
557 pos, scalarsPerPosition, | |
558 offset, regionClipBounds)); | |
559 | |
560 blob->flushThrowaway(fContext, dc, fSurfaceProps, fDistanceAdjustTable, skPa
int, paint, clip, | |
561 regionClipBounds); | |
562 } | |
563 | |
564 void GrAtlasTextContext::internalDrawDFText(GrAtlasTextBlob* blob, int runIndex, | |
565 const SkPaint& skPaint, GrColor colo
r, | |
566 const SkMatrix& viewMatrix, | |
567 const char text[], size_t byteLength
, | |
568 SkScalar x, SkScalar y, | |
569 SkScalar textRatio, | |
570 SkTDArray<char>* fallbackTxt, | |
571 SkTDArray<SkScalar>* fallbackPos, | |
572 SkPoint* offset, | |
573 const SkPaint& origPaint) { | |
574 SkASSERT(byteLength == 0 || text != nullptr); | |
575 | |
576 // nothing to draw | |
577 if (text == nullptr || byteLength == 0) { | |
578 return; | |
579 } | |
580 | |
581 SkDrawCacheProc glyphCacheProc = origPaint.getDrawCacheProc(); | |
582 SkAutoDescriptor desc; | |
583 origPaint.getScalerContextDescriptor(&desc, fSurfaceProps, nullptr, true); | |
584 SkGlyphCache* origPaintCache = SkGlyphCache::DetachCache(origPaint.getTypefa
ce(), | |
585 desc.getDesc()); | |
586 | |
587 SkTArray<SkScalar> positions; | |
588 | |
589 const char* textPtr = text; | |
590 SkFixed stopX = 0; | |
591 SkFixed stopY = 0; | |
592 SkFixed origin = 0; | |
593 switch (origPaint.getTextAlign()) { | |
594 case SkPaint::kRight_Align: origin = SK_Fixed1; break; | |
595 case SkPaint::kCenter_Align: origin = SK_FixedHalf; break; | |
596 case SkPaint::kLeft_Align: origin = 0; break; | |
597 } | |
598 | |
599 SkAutoKern autokern; | |
600 const char* stop = text + byteLength; | |
601 while (textPtr < stop) { | |
602 // don't need x, y here, since all subpixel variants will have the | |
603 // same advance | |
604 const SkGlyph& glyph = glyphCacheProc(origPaintCache, &textPtr, 0, 0); | |
605 | |
606 SkFixed width = glyph.fAdvanceX + autokern.adjust(glyph); | |
607 positions.push_back(SkFixedToScalar(stopX + SkFixedMul(origin, width))); | |
608 | |
609 SkFixed height = glyph.fAdvanceY; | |
610 positions.push_back(SkFixedToScalar(stopY + SkFixedMul(origin, height)))
; | |
611 | |
612 stopX += width; | |
613 stopY += height; | |
614 } | |
615 SkASSERT(textPtr == stop); | |
616 | |
617 SkGlyphCache::AttachCache(origPaintCache); | |
618 | |
619 // now adjust starting point depending on alignment | |
620 SkScalar alignX = SkFixedToScalar(stopX); | |
621 SkScalar alignY = SkFixedToScalar(stopY); | |
622 if (origPaint.getTextAlign() == SkPaint::kCenter_Align) { | |
623 alignX = SkScalarHalf(alignX); | |
624 alignY = SkScalarHalf(alignY); | |
625 } else if (origPaint.getTextAlign() == SkPaint::kLeft_Align) { | |
626 alignX = 0; | |
627 alignY = 0; | |
628 } | |
629 x -= alignX; | |
630 y -= alignY; | |
631 *offset = SkPoint::Make(x, y); | |
632 | |
633 this->internalDrawDFPosText(blob, runIndex, skPaint, color, viewMatrix, text
, byteLength, | |
634 positions.begin(), 2, *offset, textRatio, fallba
ckTxt, | |
635 fallbackPos); | |
636 } | |
637 | |
638 void GrAtlasTextContext::internalDrawDFPosText(GrAtlasTextBlob* blob, int runInd
ex, | |
639 const SkPaint& skPaint, GrColor c
olor, | |
640 const SkMatrix& viewMatrix, | |
641 const char text[], size_t byteLen
gth, | |
642 const SkScalar pos[], int scalars
PerPosition, | |
643 const SkPoint& offset, | |
644 SkScalar textRatio, | |
645 SkTDArray<char>* fallbackTxt, | |
646 SkTDArray<SkScalar>* fallbackPos)
{ | |
647 | |
648 SkASSERT(byteLength == 0 || text != nullptr); | |
649 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); | |
650 | |
651 // nothing to draw | |
652 if (text == nullptr || byteLength == 0) { | |
653 return; | |
654 } | |
655 | |
656 fCurrStrike = nullptr; | |
657 | |
658 SkDrawCacheProc glyphCacheProc = skPaint.getDrawCacheProc(); | |
659 SkGlyphCache* cache = this->setupCache(&blob->fRuns[runIndex], skPaint, null
ptr, true); | |
660 GrFontScaler* fontScaler = GetGrFontScaler(cache); | |
661 | |
662 const char* stop = text + byteLength; | |
663 | |
664 if (SkPaint::kLeft_Align == skPaint.getTextAlign()) { | |
665 while (text < stop) { | |
666 const char* lastText = text; | |
667 // the last 2 parameters are ignored | |
668 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); | |
669 | |
670 if (glyph.fWidth) { | |
671 SkScalar x = offset.x() + pos[0]; | |
672 SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0)
; | |
673 | |
674 if (!this->dfAppendGlyph(blob, | |
675 runIndex, | |
676 glyph, | |
677 x, y, color, fontScaler, | |
678 textRatio, viewMatrix)) { | |
679 // couldn't append, send to fallback | |
680 fallbackTxt->append(SkToInt(text-lastText), lastText); | |
681 *fallbackPos->append() = pos[0]; | |
682 if (2 == scalarsPerPosition) { | |
683 *fallbackPos->append() = pos[1]; | |
684 } | |
685 } | |
686 } | |
687 pos += scalarsPerPosition; | |
688 } | |
689 } else { | |
690 SkScalar alignMul = SkPaint::kCenter_Align == skPaint.getTextAlign() ? S
K_ScalarHalf | |
691 : S
K_Scalar1; | |
692 while (text < stop) { | |
693 const char* lastText = text; | |
694 // the last 2 parameters are ignored | |
695 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); | |
696 | |
697 if (glyph.fWidth) { | |
698 SkScalar x = offset.x() + pos[0]; | |
699 SkScalar y = offset.y() + (2 == scalarsPerPosition ? pos[1] : 0)
; | |
700 | |
701 SkScalar advanceX = SkFixedToScalar(glyph.fAdvanceX) * alignMul
* textRatio; | |
702 SkScalar advanceY = SkFixedToScalar(glyph.fAdvanceY) * alignMul
* textRatio; | |
703 | |
704 if (!this->dfAppendGlyph(blob, | |
705 runIndex, | |
706 glyph, | |
707 x - advanceX, y - advanceY, color, | |
708 fontScaler, | |
709 textRatio, | |
710 viewMatrix)) { | |
711 // couldn't append, send to fallback | |
712 fallbackTxt->append(SkToInt(text-lastText), lastText); | |
713 *fallbackPos->append() = pos[0]; | |
714 if (2 == scalarsPerPosition) { | |
715 *fallbackPos->append() = pos[1]; | |
716 } | |
717 } | |
718 } | |
719 pos += scalarsPerPosition; | |
720 } | |
721 } | |
722 | |
723 SkGlyphCache::AttachCache(cache); | |
724 } | |
725 | |
726 bool GrAtlasTextContext::dfAppendGlyph(GrAtlasTextBlob* blob, int runIndex, | |
727 const SkGlyph& skGlyph, | |
728 SkScalar sx, SkScalar sy, GrColor color, | |
729 GrFontScaler* scaler, | |
730 SkScalar textRatio, const SkMatrix& viewM
atrix) { | |
731 if (!fCurrStrike) { | |
732 fCurrStrike = fContext->getBatchFontCache()->getStrike(scaler); | |
733 } | |
734 | |
735 GrGlyph::PackedID id = GrGlyph::Pack(skGlyph.getGlyphID(), | |
736 skGlyph.getSubXFixed(), | |
737 skGlyph.getSubYFixed(), | |
738 GrGlyph::kDistance_MaskStyle); | |
739 GrGlyph* glyph = fCurrStrike->getGlyph(skGlyph, id, scaler); | |
740 if (!glyph) { | |
741 return true; | |
742 } | |
743 | |
744 // fallback to color glyph support | |
745 if (kA8_GrMaskFormat != glyph->fMaskFormat) { | |
746 return false; | |
747 } | |
748 | |
749 SkScalar dx = SkIntToScalar(glyph->fBounds.fLeft + SK_DistanceFieldInset); | |
750 SkScalar dy = SkIntToScalar(glyph->fBounds.fTop + SK_DistanceFieldInset); | |
751 SkScalar width = SkIntToScalar(glyph->fBounds.width() - 2 * SK_DistanceField
Inset); | |
752 SkScalar height = SkIntToScalar(glyph->fBounds.height() - 2 * SK_DistanceFie
ldInset); | |
753 | |
754 SkScalar scale = textRatio; | |
755 dx *= scale; | |
756 dy *= scale; | |
757 width *= scale; | |
758 height *= scale; | |
759 sx += dx; | |
760 sy += dy; | |
761 SkRect glyphRect = SkRect::MakeXYWH(sx, sy, width, height); | |
762 | |
763 blob->appendGlyph(runIndex, glyphRect, color, fCurrStrike, glyph, scaler, sk
Glyph, | |
764 sx - dx, sy - dy, scale, true); | |
765 return true; | |
766 } | |
767 | |
768 ////////////////////////////////////////////////////////////////////////////////
/////////////////// | |
769 | |
770 #ifdef GR_TEST_UTILS | |
771 | |
772 DRAW_BATCH_TEST_DEFINE(TextBlobBatch) { | |
773 static uint32_t gContextID = SK_InvalidGenID; | |
774 static GrAtlasTextContext* gTextContext = nullptr; | |
775 static SkSurfaceProps gSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType
); | |
776 | |
777 if (context->uniqueID() != gContextID) { | |
778 gContextID = context->uniqueID(); | |
779 delete gTextContext; | |
780 | |
781 // We don't yet test the fall back to paths in the GrTextContext base cl
ass. This is mostly | |
782 // because we don't really want to have a gpu device here. | |
783 // We enable distance fields by twiddling a knob on the paint | |
784 gTextContext = GrAtlasTextContext::Create(context, gSurfaceProps); | |
785 } | |
786 | |
787 // Setup dummy SkPaint / GrPaint | |
788 GrColor color = GrRandomColor(random); | |
789 SkMatrix viewMatrix = GrTest::TestMatrixInvertible(random); | |
790 SkPaint skPaint; | |
791 skPaint.setColor(color); | |
792 skPaint.setLCDRenderText(random->nextBool()); | |
793 skPaint.setAntiAlias(skPaint.isLCDRenderText() ? true : random->nextBool()); | |
794 skPaint.setSubpixelText(random->nextBool()); | |
795 | |
796 GrPaint grPaint; | |
797 if (!SkPaintToGrPaint(context, skPaint, viewMatrix, &grPaint)) { | |
798 SkFAIL("couldn't convert paint\n"); | |
799 } | |
800 | |
801 const char* text = "The quick brown fox jumps over the lazy dog."; | |
802 int textLen = (int)strlen(text); | |
803 | |
804 // Setup clip | |
805 GrClip clip; | |
806 SkIRect noClip = SkIRect::MakeLargest(); | |
807 | |
808 // right now we don't handle textblobs, nor do we handle drawPosText. Since
we only | |
809 // intend to test the batch with this unit test, that is okay. | |
810 SkAutoTUnref<GrAtlasTextBlob> blob( | |
811 gTextContext->createDrawTextBlob(clip, grPaint, skPaint, viewMatrix,
text, | |
812 static_cast<size_t>(textLen), 0, 0,
noClip)); | |
813 | |
814 SkScalar transX = static_cast<SkScalar>(random->nextU()); | |
815 SkScalar transY = static_cast<SkScalar>(random->nextU()); | |
816 const GrAtlasTextBlob::Run::SubRunInfo& info = blob->fRuns[0].fSubRunInfo[0]
; | |
817 return blob->createBatch(info, textLen, 0, 0, color, transX, transY, skPaint
, | |
818 gSurfaceProps, gTextContext->dfAdjustTable(), | |
819 context->getBatchFontCache()); | |
820 } | |
821 | |
822 #endif | |
OLD | NEW |