1 /*
2 * Copyright 2014 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
8 #include "GrStencilAndCoverTextContext.h"
9 #include "GrDrawTarget.h"
10 #include "GrGpu.h"
11 #include "GrPath.h"
12 #include "GrPathRange.h"
13 #include "SkAutoKern.h"
14 #include "SkDraw.h"
15 #include "SkDrawProcs.h"
16 #include "SkGlyphCache.h"
17 #include "SkGpuDevice.h"
18 #include "SkPath.h"
19 #include "SkTextMapStateProc.h"
20 #include "SkTextFormatParams.h"
21
GrStencilAndCoverTextContext(GrContext * context,const SkDeviceProperties & properties)22 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
23 GrContext* context, const SkDeviceProperties& properties)
24 : GrTextContext(context, properties)
25 , fPendingGlyphCount(0) {
26 }
27
~GrStencilAndCoverTextContext()28 GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
29 }
30
drawText(const GrPaint & paint,const SkPaint & skPaint,const char text[],size_t byteLength,SkScalar x,SkScalar y)31 void GrStencilAndCoverTextContext::drawText(const GrPaint& paint,
32 const SkPaint& skPaint,
33 const char text[],
34 size_t byteLength,
35 SkScalar x, SkScalar y) {
36 SkASSERT(byteLength == 0 || text != NULL);
37
38 if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
39 return;
40 }
41
42 // This is the slow path, mainly used by Skia unit tests. The other
43 // backends (8888, gpu, ...) use device-space dependent glyph caches. In
44 // order to match the glyph positions that the other code paths produce, we
45 // must also use device-space dependent glyph cache. This has the
46 // side-effect that the glyph shape outline will be in device-space,
47 // too. This in turn has the side-effect that NVPR can not stroke the paths,
48 // as the stroke in NVPR is defined in object-space.
49 // NOTE: here we have following coincidence that works at the moment:
50 // - When using the device-space glyphs, the transforms we pass to NVPR
51 // instanced drawing are the global transforms, and the view transform is
52 // identity. NVPR can not use non-affine transforms in the instanced
53 // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it
54 // will turn off the use of device-space glyphs when perspective transforms
55 // are in use.
56
57 this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode);
58
59 // Transform our starting point.
60 if (fNeedsDeviceSpaceGlyphs) {
61 SkPoint loc;
62 fContextInitialMatrix.mapXY(x, y, &loc);
63 x = loc.fX;
64 y = loc.fY;
65 }
66
67 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
68
69 fTransformType = GrPathRendering::kTranslate_PathTransformType;
70
71 const char* stop = text + byteLength;
72
73 // Measure first if needed.
74 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
75 SkFixed stopX = 0;
76 SkFixed stopY = 0;
77
78 const char* textPtr = text;
79 while (textPtr < stop) {
80 // We don't need x, y here, since all subpixel variants will have the
81 // same advance.
82 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
83
84 stopX += glyph.fAdvanceX;
85 stopY += glyph.fAdvanceY;
86 }
87 SkASSERT(textPtr == stop);
88
89 SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
90 SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
91
92 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
93 alignX = SkScalarHalf(alignX);
94 alignY = SkScalarHalf(alignY);
95 }
96
97 x -= alignX;
98 y -= alignY;
99 }
100
101 SkAutoKern autokern;
102
103 SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
104
105 SkFixed fx = SkScalarToFixed(x);
106 SkFixed fy = SkScalarToFixed(y);
107 while (text < stop) {
108 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
109 fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
110 if (glyph.fWidth) {
111 this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy));
112 }
113
114 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
115 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio);
116 }
117
118 this->finish();
119 }
120
drawPosText(const GrPaint & paint,const SkPaint & skPaint,const char text[],size_t byteLength,const SkScalar pos[],SkScalar constY,int scalarsPerPosition)121 void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint,
122 const SkPaint& skPaint,
123 const char text[],
124 size_t byteLength,
125 const SkScalar pos[],
126 SkScalar constY,
127 int scalarsPerPosition) {
128 SkASSERT(byteLength == 0 || text != NULL);
129 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
130
131 // nothing to draw
132 if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
133 return;
134 }
135
136 // This is the fast path. Here we do not bake in the device-transform to
137 // the glyph outline or the advances. This is because we do not need to
138 // position the glyphs at all, since the caller has done the positioning.
139 // The positioning is based on SkPaint::measureText of individual
140 // glyphs. That already uses glyph cache without device transforms. Device
141 // transform is not part of SkPaint::measureText API, and thus we use the
142 // same glyphs as what were measured.
143
144 const float textTranslateY = (1 == scalarsPerPosition ? constY : 0);
145 this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode, textTranslateY);
146
147 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
148
149 const char* stop = text + byteLength;
150
151 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
152 if (1 == scalarsPerPosition) {
153 fTransformType = GrPathRendering::kTranslateX_PathTransformType;
154 while (text < stop) {
155 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
156 if (glyph.fWidth) {
157 this->appendGlyph(glyph.getGlyphID(), *pos);
158 }
159 pos++;
160 }
161 } else {
162 SkASSERT(2 == scalarsPerPosition);
163 fTransformType = GrPathRendering::kTranslate_PathTransformType;
164 while (text < stop) {
165 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
166 if (glyph.fWidth) {
167 this->appendGlyph(glyph.getGlyphID(), pos[0], pos[1]);
168 }
169 pos += 2;
170 }
171 }
172 } else {
173 fTransformType = GrPathRendering::kTranslate_PathTransformType;
174 SkTextMapStateProc tmsProc(SkMatrix::I(), 0, scalarsPerPosition);
175 SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
176 while (text < stop) {
177 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
178 if (glyph.fWidth) {
179 SkPoint tmsLoc;
180 tmsProc(pos, &tmsLoc);
181 SkPoint loc;
182 alignProc(tmsLoc, glyph, &loc);
183
184 this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
185 }
186 pos += scalarsPerPosition;
187 }
188 }
189
190 this->finish();
191 }
192
canDraw(const SkPaint & paint)193 bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
194 if (paint.getRasterizer()) {
195 return false;
196 }
197 if (paint.getMaskFilter()) {
198 return false;
199 }
200 if (paint.getPathEffect()) {
201 return false;
202 }
203
204 // No hairlines unless we can map the 1 px width to the object space.
205 if (paint.getStyle() == SkPaint::kStroke_Style
206 && paint.getStrokeWidth() == 0
207 && fContext->getMatrix().hasPerspective()) {
208 return false;
209 }
210
211 // No color bitmap fonts.
212 SkScalerContext::Rec rec;
213 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
214 return rec.getFormat() != SkMask::kARGB32_Format;
215 }
216
get_gr_glyphs(GrContext * ctx,const SkTypeface * typeface,const SkDescriptor * desc,const SkStrokeRec & stroke)217 static GrPathRange* get_gr_glyphs(GrContext* ctx,
218 const SkTypeface* typeface,
219 const SkDescriptor* desc,
220 const SkStrokeRec& stroke) {
221 static const GrCacheID::Domain gGlyphsDomain = GrCacheID::GenerateDomain();
222
223 GrCacheID::Key key;
224 uint64_t* keyData = key.fData64;
225 keyData[0] = desc ? desc->getChecksum() : 0;
226 keyData[0] = (keyData[0] << 32) | (typeface ? typeface->uniqueID() : 0);
227 keyData[1] = GrPath::ComputeStrokeKey(stroke);
228 GrResourceKey resourceKey = GrResourceKey(GrCacheID(gGlyphsDomain, key),
229 GrPathRange::resourceType(), 0);
230
231 SkAutoTUnref<GrPathRange> glyphs(
232 static_cast<GrPathRange*>(ctx->findAndRefCachedResource(resourceKey)));
233 if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) {
234 glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke));
235 ctx->addResourceToCache(resourceKey, glyphs);
236 }
237
238 return glyphs.detach();
239 }
240
init(const GrPaint & paint,const SkPaint & skPaint,size_t textByteLength,RenderMode renderMode,SkScalar textTranslateY)241 void GrStencilAndCoverTextContext::init(const GrPaint& paint,
242 const SkPaint& skPaint,
243 size_t textByteLength,
244 RenderMode renderMode,
245 SkScalar textTranslateY) {
246 GrTextContext::init(paint, skPaint);
247
248 fContextInitialMatrix = fContext->getMatrix();
249
250 const bool otherBackendsWillDrawAsPaths =
251 SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
252
253 fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
254 kMaxAccuracy_RenderMode == renderMode &&
255 SkToBool(fContextInitialMatrix.getType() &
256 (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
257
258 if (fNeedsDeviceSpaceGlyphs) {
259 // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
260 SkASSERT(!fContextInitialMatrix.hasPerspective());
261 SkASSERT(0 == textTranslateY); // TODO: Handle textTranslateY in device-space usecase.
262
263 fTextRatio = fTextInverseRatio = 1.0f;
264
265 // Glyphs loaded by GPU path rendering have an inverted y-direction.
266 SkMatrix m;
267 m.setScale(1, -1);
268 fContext->setMatrix(m);
269
270 // Post-flip the initial matrix so we're left with just the flip after
271 // the paint preConcats the inverse.
272 m = fContextInitialMatrix;
273 m.postScale(1, -1);
274 fPaint.localCoordChangeInverse(m);
275
276 // The whole shape (including stroke) will be baked into the glyph outlines. Make
277 // NVPR just fill the baked shapes.
278 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false);
279 fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
280 &fGlyphCache->getDescriptor(),
281 SkStrokeRec(SkStrokeRec::kFill_InitStyle));
282 } else {
283 // Don't bake strokes into the glyph outlines. We will stroke the glyphs
284 // using the GPU instead. This is the fast path.
285 SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint);
286 fSkPaint.setStyle(SkPaint::kFill_Style);
287
288 if (gpuStroke.isHairlineStyle()) {
289 // Approximate hairline stroke.
290 SkScalar strokeWidth = SK_Scalar1 /
291 (SkVector::Make(fContextInitialMatrix.getScaleX(),
292 fContextInitialMatrix.getSkewY()).length());
293 gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/);
294
295 } else if (fSkPaint.isFakeBoldText() &&
296 #ifdef SK_USE_FREETYPE_EMBOLDEN
297 kMaxPerformance_RenderMode == renderMode &&
298 #endif
299 SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) {
300
301 // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke.
302 SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(),
303 kStdFakeBoldInterpKeys,
304 kStdFakeBoldInterpValues,
305 kStdFakeBoldInterpLength);
306 SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale);
307 gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra,
308 true /*strokeAndFill*/);
309
310 fSkPaint.setFakeBoldText(false);
311 }
312
313 bool canUseRawPaths;
314
315 if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) {
316 // We can draw the glyphs from canonically sized paths.
317 fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
318 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
319
320 // Compensate for the glyphs being scaled by fTextRatio.
321 if (!gpuStroke.isFillStyle()) {
322 gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio,
323 SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle());
324 }
325
326 fSkPaint.setLinearText(true);
327 fSkPaint.setLCDRenderText(false);
328 fSkPaint.setAutohinted(false);
329 fSkPaint.setHinting(SkPaint::kNo_Hinting);
330 fSkPaint.setSubpixelText(true);
331 fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
332
333 canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() &&
334 0 == fSkPaint.getTextSkewX() &&
335 !fSkPaint.isFakeBoldText() &&
336 !fSkPaint.isVerticalText();
337 } else {
338 fTextRatio = fTextInverseRatio = 1.0f;
339 canUseRawPaths = false;
340 }
341
342 SkMatrix textMatrix;
343 textMatrix.setTranslate(0, textTranslateY);
344 // Glyphs loaded by GPU path rendering have an inverted y-direction.
345 textMatrix.preScale(fTextRatio, -fTextRatio);
346 fPaint.localCoordChange(textMatrix);
347 fContext->concatMatrix(textMatrix);
348
349 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false);
350 fGlyphs = canUseRawPaths ?
351 get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) :
352 get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
353 &fGlyphCache->getDescriptor(), gpuStroke);
354 }
355
356 fStateRestore.set(fDrawTarget->drawState());
357
358 fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(),
359 fContext->getRenderTarget());
360
361 GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
362 kZero_StencilOp,
363 kZero_StencilOp,
364 kNotEqual_StencilFunc,
365 0xffff,
366 0x0000,
367 0xffff);
368
369 *fDrawTarget->drawState()->stencil() = kStencilPass;
370
371 SkASSERT(0 == fPendingGlyphCount);
372 }
373
appendGlyph(uint16_t glyphID,float x)374 inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x) {
375 SkASSERT(GrPathRendering::kTranslateX_PathTransformType == fTransformType);
376
377 if (fPendingGlyphCount >= kGlyphBufferSize) {
378 this->flush();
379 }
380
381 fIndexBuffer[fPendingGlyphCount] = glyphID;
382 fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x;
383
384 ++fPendingGlyphCount;
385 }
386
appendGlyph(uint16_t glyphID,float x,float y)387 inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
388 SkASSERT(GrPathRendering::kTranslate_PathTransformType == fTransformType);
389
390 if (fPendingGlyphCount >= kGlyphBufferSize) {
391 this->flush();
392 }
393
394 fIndexBuffer[fPendingGlyphCount] = glyphID;
395 fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
396 fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y;
397
398 ++fPendingGlyphCount;
399 }
400
flush()401 void GrStencilAndCoverTextContext::flush() {
402 if (0 == fPendingGlyphCount) {
403 return;
404 }
405
406 fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount,
407 fTransformBuffer, fTransformType, SkPath::kWinding_FillType);
408
409 fPendingGlyphCount = 0;
410 }
411
finish()412 void GrStencilAndCoverTextContext::finish() {
413 this->flush();
414
415 fGlyphs->unref();
416 fGlyphs = NULL;
417
418 SkGlyphCache::AttachCache(fGlyphCache);
419 fGlyphCache = NULL;
420
421 fDrawTarget->drawState()->stencil()->setDisabled();
422 fStateRestore.set(NULL);
423 fContext->setMatrix(fContextInitialMatrix);
424 GrTextContext::finish();
425 }
426
427