blob: 14a14a0474d9e840648f6c28b76e12c4c004fa31 [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"
jvanverth8c27a182014-10-14 08:45:50 -07009#include "GrBitmapTextContext.h"
kkinnunenc6cb56f2014-06-24 00:12:27 -070010#include "GrDrawTarget.h"
kkinnunenc6cb56f2014-06-24 00:12:27 -070011#include "GrGpu.h"
12#include "GrPath.h"
cdaltonb85a0aa2014-07-21 15:32:44 -070013#include "GrPathRange.h"
jvanverthaab626c2014-10-16 08:04:39 -070014#include "SkAutoKern.h"
kkinnunenc6cb56f2014-06-24 00:12:27 -070015#include "SkDraw.h"
16#include "SkDrawProcs.h"
17#include "SkGlyphCache.h"
18#include "SkGpuDevice.h"
19#include "SkPath.h"
20#include "SkTextMapStateProc.h"
cdalton855d83f2014-09-18 13:51:53 -070021#include "SkTextFormatParams.h"
kkinnunenc6cb56f2014-06-24 00:12:27 -070022
23GrStencilAndCoverTextContext::GrStencilAndCoverTextContext(
24 GrContext* context, const SkDeviceProperties& properties)
25 : GrTextContext(context, properties)
cdaltonb85a0aa2014-07-21 15:32:44 -070026 , fPendingGlyphCount(0) {
kkinnunenc6cb56f2014-06-24 00:12:27 -070027}
28
jvanverth8c27a182014-10-14 08:45:50 -070029GrStencilAndCoverTextContext* GrStencilAndCoverTextContext::Create(GrContext* context,
30 const SkDeviceProperties& props) {
31 GrStencilAndCoverTextContext* textContext = SkNEW_ARGS(GrStencilAndCoverTextContext,
32 (context, props));
33 textContext->fFallbackTextContext = GrBitmapTextContext::Create(context, props);
34
35 return textContext;
36}
37
kkinnunenc6cb56f2014-06-24 00:12:27 -070038GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() {
39}
40
jvanverth0fedb192014-10-08 09:07:27 -070041bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
42 if (paint.getRasterizer()) {
43 return false;
44 }
45 if (paint.getMaskFilter()) {
46 return false;
47 }
48 if (paint.getPathEffect()) {
49 return false;
50 }
51
52 // No hairlines unless we can map the 1 px width to the object space.
53 if (paint.getStyle() == SkPaint::kStroke_Style
54 && paint.getStrokeWidth() == 0
55 && fContext->getMatrix().hasPerspective()) {
56 return false;
57 }
58
59 // No color bitmap fonts.
60 SkScalerContext::Rec rec;
61 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
62 return rec.getFormat() != SkMask::kARGB32_Format;
63}
64
jvanverthaab626c2014-10-16 08:04:39 -070065void GrStencilAndCoverTextContext::onDrawText(const GrPaint& paint,
66 const SkPaint& skPaint,
67 const char text[],
68 size_t byteLength,
69 SkScalar x, SkScalar y) {
70 SkASSERT(byteLength == 0 || text != NULL);
71
72 if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
73 return;
74 }
75
76 // This is the slow path, mainly used by Skia unit tests. The other
77 // backends (8888, gpu, ...) use device-space dependent glyph caches. In
78 // order to match the glyph positions that the other code paths produce, we
79 // must also use device-space dependent glyph cache. This has the
80 // side-effect that the glyph shape outline will be in device-space,
81 // too. This in turn has the side-effect that NVPR can not stroke the paths,
82 // as the stroke in NVPR is defined in object-space.
83 // NOTE: here we have following coincidence that works at the moment:
84 // - When using the device-space glyphs, the transforms we pass to NVPR
85 // instanced drawing are the global transforms, and the view transform is
86 // identity. NVPR can not use non-affine transforms in the instanced
87 // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it
88 // will turn off the use of device-space glyphs when perspective transforms
89 // are in use.
90
cdalton38e13ad2014-11-07 06:02:15 -080091 this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode);
jvanverthaab626c2014-10-16 08:04:39 -070092
93 // Transform our starting point.
94 if (fNeedsDeviceSpaceGlyphs) {
95 SkPoint loc;
96 fContextInitialMatrix.mapXY(x, y, &loc);
97 x = loc.fX;
98 y = loc.fY;
99 }
100
101 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
102
jvanverthaab626c2014-10-16 08:04:39 -0700103 const char* stop = text + byteLength;
104
105 // Measure first if needed.
106 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
107 SkFixed stopX = 0;
108 SkFixed stopY = 0;
109
110 const char* textPtr = text;
111 while (textPtr < stop) {
112 // We don't need x, y here, since all subpixel variants will have the
113 // same advance.
114 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
115
116 stopX += glyph.fAdvanceX;
117 stopY += glyph.fAdvanceY;
118 }
119 SkASSERT(textPtr == stop);
120
121 SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
122 SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
123
124 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
125 alignX = SkScalarHalf(alignX);
126 alignY = SkScalarHalf(alignY);
127 }
128
129 x -= alignX;
130 y -= alignY;
131 }
132
133 SkAutoKern autokern;
134
135 SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
136
137 SkFixed fx = SkScalarToFixed(x);
138 SkFixed fy = SkScalarToFixed(y);
139 while (text < stop) {
140 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
141 fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
142 if (glyph.fWidth) {
143 this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy));
144 }
145
146 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
147 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio);
148 }
149
150 this->finish();
151}
152
jvanverth8c27a182014-10-14 08:45:50 -0700153void GrStencilAndCoverTextContext::onDrawPosText(const GrPaint& paint,
kkinnunenc6cb56f2014-06-24 00:12:27 -0700154 const SkPaint& skPaint,
155 const char text[],
156 size_t byteLength,
157 const SkScalar pos[],
fmalita05c4a432014-09-29 06:29:53 -0700158 int scalarsPerPosition,
159 const SkPoint& offset) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700160 SkASSERT(byteLength == 0 || text != NULL);
161 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
162
163 // nothing to draw
164 if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
165 return;
166 }
167
168 // This is the fast path. Here we do not bake in the device-transform to
169 // the glyph outline or the advances. This is because we do not need to
170 // position the glyphs at all, since the caller has done the positioning.
171 // The positioning is based on SkPaint::measureText of individual
172 // glyphs. That already uses glyph cache without device transforms. Device
173 // transform is not part of SkPaint::measureText API, and thus we use the
174 // same glyphs as what were measured.
kkinnunenc6cb56f2014-06-24 00:12:27 -0700175
cdalton38e13ad2014-11-07 06:02:15 -0800176 this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700177
178 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
179
kkinnunenc6cb56f2014-06-24 00:12:27 -0700180 const char* stop = text + byteLength;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700181
cdalton38e13ad2014-11-07 06:02:15 -0800182 SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);
183 SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
184 while (text < stop) {
185 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
186 if (glyph.fWidth) {
187 SkPoint tmsLoc;
188 tmsProc(pos, &tmsLoc);
189 SkPoint loc;
190 alignProc(tmsLoc, glyph, &loc);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700191
cdalton38e13ad2014-11-07 06:02:15 -0800192 this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
kkinnunenc6cb56f2014-06-24 00:12:27 -0700193 }
cdalton38e13ad2014-11-07 06:02:15 -0800194 pos += scalarsPerPosition;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700195 }
196
197 this->finish();
198}
199
cdalton855d83f2014-09-18 13:51:53 -0700200static GrPathRange* get_gr_glyphs(GrContext* ctx,
201 const SkTypeface* typeface,
202 const SkDescriptor* desc,
203 const SkStrokeRec& stroke) {
204 static const GrCacheID::Domain gGlyphsDomain = GrCacheID::GenerateDomain();
205
206 GrCacheID::Key key;
207 uint64_t* keyData = key.fData64;
208 keyData[0] = desc ? desc->getChecksum() : 0;
209 keyData[0] = (keyData[0] << 32) | (typeface ? typeface->uniqueID() : 0);
210 keyData[1] = GrPath::ComputeStrokeKey(stroke);
211 GrResourceKey resourceKey = GrResourceKey(GrCacheID(gGlyphsDomain, key),
212 GrPathRange::resourceType(), 0);
213
214 SkAutoTUnref<GrPathRange> glyphs(
215 static_cast<GrPathRange*>(ctx->findAndRefCachedResource(resourceKey)));
216 if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) {
217 glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke));
218 ctx->addResourceToCache(resourceKey, glyphs);
219 }
220
221 return glyphs.detach();
222}
223
kkinnunenc6cb56f2014-06-24 00:12:27 -0700224void GrStencilAndCoverTextContext::init(const GrPaint& paint,
225 const SkPaint& skPaint,
cdaltonb2808cd2014-07-25 14:13:57 -0700226 size_t textByteLength,
cdalton38e13ad2014-11-07 06:02:15 -0800227 RenderMode renderMode) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700228 GrTextContext::init(paint, skPaint);
229
cdaltonb2808cd2014-07-25 14:13:57 -0700230 fContextInitialMatrix = fContext->getMatrix();
231
cdalton855d83f2014-09-18 13:51:53 -0700232 const bool otherBackendsWillDrawAsPaths =
cdaltonb2808cd2014-07-25 14:13:57 -0700233 SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700234
cdalton855d83f2014-09-18 13:51:53 -0700235 fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
236 kMaxAccuracy_RenderMode == renderMode &&
237 SkToBool(fContextInitialMatrix.getType() &
238 (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700239
240 if (fNeedsDeviceSpaceGlyphs) {
cdalton855d83f2014-09-18 13:51:53 -0700241 // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
242 SkASSERT(!fContextInitialMatrix.hasPerspective());
cdaltonb2808cd2014-07-25 14:13:57 -0700243
cdalton855d83f2014-09-18 13:51:53 -0700244 fTextRatio = fTextInverseRatio = 1.0f;
245
246 // Glyphs loaded by GPU path rendering have an inverted y-direction.
247 SkMatrix m;
248 m.setScale(1, -1);
249 fContext->setMatrix(m);
250
251 // Post-flip the initial matrix so we're left with just the flip after
252 // the paint preConcats the inverse.
253 m = fContextInitialMatrix;
254 m.postScale(1, -1);
255 fPaint.localCoordChangeInverse(m);
256
257 // The whole shape (including stroke) will be baked into the glyph outlines. Make
258 // NVPR just fill the baked shapes.
259 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false);
260 fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
261 &fGlyphCache->getDescriptor(),
262 SkStrokeRec(SkStrokeRec::kFill_InitStyle));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700263 } else {
cdalton855d83f2014-09-18 13:51:53 -0700264 // Don't bake strokes into the glyph outlines. We will stroke the glyphs
265 // using the GPU instead. This is the fast path.
266 SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700267 fSkPaint.setStyle(SkPaint::kFill_Style);
cdalton855d83f2014-09-18 13:51:53 -0700268
269 if (gpuStroke.isHairlineStyle()) {
270 // Approximate hairline stroke.
271 SkScalar strokeWidth = SK_Scalar1 /
272 (SkVector::Make(fContextInitialMatrix.getScaleX(),
273 fContextInitialMatrix.getSkewY()).length());
274 gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/);
275
276 } else if (fSkPaint.isFakeBoldText() &&
277#ifdef SK_USE_FREETYPE_EMBOLDEN
278 kMaxPerformance_RenderMode == renderMode &&
279#endif
280 SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) {
281
282 // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke.
283 SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(),
284 kStdFakeBoldInterpKeys,
285 kStdFakeBoldInterpValues,
286 kStdFakeBoldInterpLength);
287 SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale);
288 gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra,
289 true /*strokeAndFill*/);
290
291 fSkPaint.setFakeBoldText(false);
292 }
293
294 bool canUseRawPaths;
295
296 if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) {
297 // We can draw the glyphs from canonically sized paths.
298 fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
299 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
300
301 // Compensate for the glyphs being scaled by fTextRatio.
302 if (!gpuStroke.isFillStyle()) {
303 gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio,
304 SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle());
305 }
306
307 fSkPaint.setLinearText(true);
308 fSkPaint.setLCDRenderText(false);
309 fSkPaint.setAutohinted(false);
310 fSkPaint.setHinting(SkPaint::kNo_Hinting);
311 fSkPaint.setSubpixelText(true);
312 fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
313
314 canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() &&
315 0 == fSkPaint.getTextSkewX() &&
316 !fSkPaint.isFakeBoldText() &&
317 !fSkPaint.isVerticalText();
318 } else {
319 fTextRatio = fTextInverseRatio = 1.0f;
320 canUseRawPaths = false;
321 }
322
323 SkMatrix textMatrix;
cdalton855d83f2014-09-18 13:51:53 -0700324 // Glyphs loaded by GPU path rendering have an inverted y-direction.
cdalton38e13ad2014-11-07 06:02:15 -0800325 textMatrix.setScale(fTextRatio, -fTextRatio);
cdalton855d83f2014-09-18 13:51:53 -0700326 fPaint.localCoordChange(textMatrix);
327 fContext->concatMatrix(textMatrix);
328
329 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false);
330 fGlyphs = canUseRawPaths ?
331 get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) :
332 get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
333 &fGlyphCache->getDescriptor(), gpuStroke);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700334 }
cdalton855d83f2014-09-18 13:51:53 -0700335
kkinnunenc6cb56f2014-06-24 00:12:27 -0700336 fStateRestore.set(fDrawTarget->drawState());
337
338 fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(),
339 fContext->getRenderTarget());
340
341 GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
342 kZero_StencilOp,
343 kZero_StencilOp,
344 kNotEqual_StencilFunc,
345 0xffff,
346 0x0000,
347 0xffff);
348
349 *fDrawTarget->drawState()->stencil() = kStencilPass;
350
cdaltonb85a0aa2014-07-21 15:32:44 -0700351 SkASSERT(0 == fPendingGlyphCount);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700352}
353
cdaltonb2808cd2014-07-25 14:13:57 -0700354inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
cdaltonb2808cd2014-07-25 14:13:57 -0700355 if (fPendingGlyphCount >= kGlyphBufferSize) {
356 this->flush();
357 }
358
cdaltonb2808cd2014-07-25 14:13:57 -0700359 fIndexBuffer[fPendingGlyphCount] = glyphID;
360 fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
cdalton855d83f2014-09-18 13:51:53 -0700361 fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y;
cdaltonb85a0aa2014-07-21 15:32:44 -0700362
363 ++fPendingGlyphCount;
364}
365
366void GrStencilAndCoverTextContext::flush() {
367 if (0 == fPendingGlyphCount) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700368 return;
369 }
370
cdalton38e13ad2014-11-07 06:02:15 -0800371 fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount, fTransformBuffer,
372 GrPathRendering::kTranslate_PathTransformType,
373 GrPathRendering::kWinding_FillType);
cdaltonb85a0aa2014-07-21 15:32:44 -0700374
375 fPendingGlyphCount = 0;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700376}
377
378void GrStencilAndCoverTextContext::finish() {
cdaltonb85a0aa2014-07-21 15:32:44 -0700379 this->flush();
kkinnunenc6cb56f2014-06-24 00:12:27 -0700380
cdalton855d83f2014-09-18 13:51:53 -0700381 fGlyphs->unref();
cdaltonb85a0aa2014-07-21 15:32:44 -0700382 fGlyphs = NULL;
cdalton855d83f2014-09-18 13:51:53 -0700383
384 SkGlyphCache::AttachCache(fGlyphCache);
cdaltonb85a0aa2014-07-21 15:32:44 -0700385 fGlyphCache = NULL;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700386
387 fDrawTarget->drawState()->stencil()->setDisabled();
388 fStateRestore.set(NULL);
cdaltonb2808cd2014-07-25 14:13:57 -0700389 fContext->setMatrix(fContextInitialMatrix);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700390 GrTextContext::finish();
391}
392