blob: c1d9e9d32d8b8a9eff2ee1ded132f7a74eafa81d [file] [log] [blame]
kkinnunenc6cb56f2014-06-24 00:12:27 -07001/*
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"
kkinnunenc6cb56f2014-06-24 00:12:27 -070010#include "GrGpu.h"
11#include "GrPath.h"
cdaltonb85a0aa2014-07-21 15:32:44 -070012#include "GrPathRange.h"
kkinnunenc6cb56f2014-06-24 00:12:27 -070013#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"
cdalton855d83f2014-09-18 13:51:53 -070020#include "SkTextFormatParams.h"
kkinnunenc6cb56f2014-06-24 00:12:27 -070021
22GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
23 GrContext* context, const SkDeviceProperties& properties)
24 : GrTextContext(context, properties)
cdaltonb85a0aa2014-07-21 15:32:44 -070025 , fPendingGlyphCount(0) {
kkinnunenc6cb56f2014-06-24 00:12:27 -070026}
27
28GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
29}
30
31void 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
cdalton855d83f2014-09-18 13:51:53 -070057 this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode);
kkinnunenc6cb56f2014-06-24 00:12:27 -070058
kkinnunenc6cb56f2014-06-24 00:12:27 -070059 // Transform our starting point.
60 if (fNeedsDeviceSpaceGlyphs) {
61 SkPoint loc;
cdaltonb2808cd2014-07-25 14:13:57 -070062 fContextInitialMatrix.mapXY(x, y, &loc);
kkinnunenc6cb56f2014-06-24 00:12:27 -070063 x = loc.fX;
64 y = loc.fY;
kkinnunenc6cb56f2014-06-24 00:12:27 -070065 }
66
67 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
cdalton855d83f2014-09-18 13:51:53 -070068
kkinnunenccdaa042014-08-20 01:36:23 -070069 fTransformType = GrPathRendering::kTranslate_PathTransformType;
kkinnunenc6cb56f2014-06-24 00:12:27 -070070
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.
cdaltonb85a0aa2014-07-21 15:32:44 -070082 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
kkinnunenc6cb56f2014-06-24 00:12:27 -070083
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) {
cdaltonb85a0aa2014-07-21 15:32:44 -0700108 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700109 fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
110 if (glyph.fWidth) {
cdaltonb85a0aa2014-07-21 15:32:44 -0700111 this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700112 }
113
114 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
115 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio);
116 }
117
118 this->finish();
119}
120
121void 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.
kkinnunenc6cb56f2014-06-24 00:12:27 -0700143
cdaltonb2808cd2014-07-25 14:13:57 -0700144 const float textTranslateY = (1 == scalarsPerPosition ? constY : 0);
cdalton855d83f2014-09-18 13:51:53 -0700145 this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode, textTranslateY);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700146
147 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
148
kkinnunenc6cb56f2014-06-24 00:12:27 -0700149 const char* stop = text + byteLength;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700150
151 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
cdaltonb2808cd2014-07-25 14:13:57 -0700152 if (1 == scalarsPerPosition) {
kkinnunenccdaa042014-08-20 01:36:23 -0700153 fTransformType = GrPathRendering::kTranslateX_PathTransformType;
cdaltonb2808cd2014-07-25 14:13:57 -0700154 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++;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700160 }
cdaltonb2808cd2014-07-25 14:13:57 -0700161 } else {
162 SkASSERT(2 == scalarsPerPosition);
kkinnunenccdaa042014-08-20 01:36:23 -0700163 fTransformType = GrPathRendering::kTranslate_PathTransformType;
cdaltonb2808cd2014-07-25 14:13:57 -0700164 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 }
kkinnunenc6cb56f2014-06-24 00:12:27 -0700171 }
172 } else {
kkinnunenccdaa042014-08-20 01:36:23 -0700173 fTransformType = GrPathRendering::kTranslate_PathTransformType;
cdaltonb2808cd2014-07-25 14:13:57 -0700174 SkTextMapStateProc tmsProc(SkMatrix::I(), 0, scalarsPerPosition);
175 SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
kkinnunenc6cb56f2014-06-24 00:12:27 -0700176 while (text < stop) {
cdaltonb85a0aa2014-07-21 15:32:44 -0700177 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700178 if (glyph.fWidth) {
179 SkPoint tmsLoc;
180 tmsProc(pos, &tmsLoc);
181 SkPoint loc;
182 alignProc(tmsLoc, glyph, &loc);
183
cdaltonb85a0aa2014-07-21 15:32:44 -0700184 this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
kkinnunenc6cb56f2014-06-24 00:12:27 -0700185 }
186 pos += scalarsPerPosition;
187 }
188 }
189
190 this->finish();
191}
192
193bool 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
cdalton855d83f2014-09-18 13:51:53 -0700217static 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
kkinnunenc6cb56f2014-06-24 00:12:27 -0700241void GrStencilAndCoverTextContext::init(const GrPaint& paint,
242 const SkPaint& skPaint,
cdaltonb2808cd2014-07-25 14:13:57 -0700243 size_t textByteLength,
cdalton855d83f2014-09-18 13:51:53 -0700244 RenderMode renderMode,
cdaltonb2808cd2014-07-25 14:13:57 -0700245 SkScalar textTranslateY) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700246 GrTextContext::init(paint, skPaint);
247
cdaltonb2808cd2014-07-25 14:13:57 -0700248 fContextInitialMatrix = fContext->getMatrix();
249
cdalton855d83f2014-09-18 13:51:53 -0700250 const bool otherBackendsWillDrawAsPaths =
cdaltonb2808cd2014-07-25 14:13:57 -0700251 SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700252
cdalton855d83f2014-09-18 13:51:53 -0700253 fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
254 kMaxAccuracy_RenderMode == renderMode &&
255 SkToBool(fContextInitialMatrix.getType() &
256 (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700257
258 if (fNeedsDeviceSpaceGlyphs) {
cdalton855d83f2014-09-18 13:51:53 -0700259 // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
260 SkASSERT(!fContextInitialMatrix.hasPerspective());
261 SkASSERT(0 == textTranslateY); // TODO: Handle textTranslateY in device-space usecase.
cdaltonb2808cd2014-07-25 14:13:57 -0700262
cdalton855d83f2014-09-18 13:51:53 -0700263 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));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700282 } else {
cdalton855d83f2014-09-18 13:51:53 -0700283 // 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);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700286 fSkPaint.setStyle(SkPaint::kFill_Style);
cdalton855d83f2014-09-18 13:51:53 -0700287
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);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700354 }
cdalton855d83f2014-09-18 13:51:53 -0700355
kkinnunenc6cb56f2014-06-24 00:12:27 -0700356 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
cdaltonb85a0aa2014-07-21 15:32:44 -0700371 SkASSERT(0 == fPendingGlyphCount);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700372}
373
cdaltonb2808cd2014-07-25 14:13:57 -0700374inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x) {
kkinnunenccdaa042014-08-20 01:36:23 -0700375 SkASSERT(GrPathRendering::kTranslateX_PathTransformType == fTransformType);
cdaltonb2808cd2014-07-25 14:13:57 -0700376
cdaltonb85a0aa2014-07-21 15:32:44 -0700377 if (fPendingGlyphCount >= kGlyphBufferSize) {
378 this->flush();
379 }
380
cdaltonb85a0aa2014-07-21 15:32:44 -0700381 fIndexBuffer[fPendingGlyphCount] = glyphID;
cdaltonb2808cd2014-07-25 14:13:57 -0700382 fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x;
383
384 ++fPendingGlyphCount;
385}
386
387inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
kkinnunenccdaa042014-08-20 01:36:23 -0700388 SkASSERT(GrPathRendering::kTranslate_PathTransformType == fTransformType);
cdaltonb2808cd2014-07-25 14:13:57 -0700389
390 if (fPendingGlyphCount >= kGlyphBufferSize) {
391 this->flush();
392 }
393
cdaltonb2808cd2014-07-25 14:13:57 -0700394 fIndexBuffer[fPendingGlyphCount] = glyphID;
395 fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
cdalton855d83f2014-09-18 13:51:53 -0700396 fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y;
cdaltonb85a0aa2014-07-21 15:32:44 -0700397
398 ++fPendingGlyphCount;
399}
400
401void GrStencilAndCoverTextContext::flush() {
402 if (0 == fPendingGlyphCount) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700403 return;
404 }
405
cdalton855d83f2014-09-18 13:51:53 -0700406 fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount,
cdaltonb2808cd2014-07-25 14:13:57 -0700407 fTransformBuffer, fTransformType, SkPath::kWinding_FillType);
cdaltonb85a0aa2014-07-21 15:32:44 -0700408
409 fPendingGlyphCount = 0;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700410}
411
412void GrStencilAndCoverTextContext::finish() {
cdaltonb85a0aa2014-07-21 15:32:44 -0700413 this->flush();
kkinnunenc6cb56f2014-06-24 00:12:27 -0700414
cdalton855d83f2014-09-18 13:51:53 -0700415 fGlyphs->unref();
cdaltonb85a0aa2014-07-21 15:32:44 -0700416 fGlyphs = NULL;
cdalton855d83f2014-09-18 13:51:53 -0700417
418 SkGlyphCache::AttachCache(fGlyphCache);
cdaltonb85a0aa2014-07-21 15:32:44 -0700419 fGlyphCache = NULL;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700420
421 fDrawTarget->drawState()->stencil()->setDisabled();
422 fStateRestore.set(NULL);
cdaltonb2808cd2014-07-25 14:13:57 -0700423 fContext->setMatrix(fContextInitialMatrix);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700424 GrTextContext::finish();
425}
426