blob: 1941fa7afc7db8d4ebad44059d9137eaab4fe6b7 [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
jvanverth0fedb192014-10-08 09:07:27 -070031bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) {
32 if (paint.getRasterizer()) {
33 return false;
34 }
35 if (paint.getMaskFilter()) {
36 return false;
37 }
38 if (paint.getPathEffect()) {
39 return false;
40 }
41
42 // No hairlines unless we can map the 1 px width to the object space.
43 if (paint.getStyle() == SkPaint::kStroke_Style
44 && paint.getStrokeWidth() == 0
45 && fContext->getMatrix().hasPerspective()) {
46 return false;
47 }
48
49 // No color bitmap fonts.
50 SkScalerContext::Rec rec;
51 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec);
52 return rec.getFormat() != SkMask::kARGB32_Format;
53}
54
kkinnunenc6cb56f2014-06-24 00:12:27 -070055void GrStencilAndCoverTextContext::drawText(const GrPaint& paint,
56 const SkPaint& skPaint,
57 const char text[],
58 size_t byteLength,
59 SkScalar x, SkScalar y) {
60 SkASSERT(byteLength == 0 || text != NULL);
61
62 if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) {
63 return;
64 }
65
66 // This is the slow path, mainly used by Skia unit tests. The other
67 // backends (8888, gpu, ...) use device-space dependent glyph caches. In
68 // order to match the glyph positions that the other code paths produce, we
69 // must also use device-space dependent glyph cache. This has the
70 // side-effect that the glyph shape outline will be in device-space,
71 // too. This in turn has the side-effect that NVPR can not stroke the paths,
72 // as the stroke in NVPR is defined in object-space.
73 // NOTE: here we have following coincidence that works at the moment:
74 // - When using the device-space glyphs, the transforms we pass to NVPR
75 // instanced drawing are the global transforms, and the view transform is
76 // identity. NVPR can not use non-affine transforms in the instanced
77 // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it
78 // will turn off the use of device-space glyphs when perspective transforms
79 // are in use.
80
fmalita05c4a432014-09-29 06:29:53 -070081 this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode, SkPoint::Make(0, 0));
kkinnunenc6cb56f2014-06-24 00:12:27 -070082
kkinnunenc6cb56f2014-06-24 00:12:27 -070083 // Transform our starting point.
84 if (fNeedsDeviceSpaceGlyphs) {
85 SkPoint loc;
cdaltonb2808cd2014-07-25 14:13:57 -070086 fContextInitialMatrix.mapXY(x, y, &loc);
kkinnunenc6cb56f2014-06-24 00:12:27 -070087 x = loc.fX;
88 y = loc.fY;
kkinnunenc6cb56f2014-06-24 00:12:27 -070089 }
90
91 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
cdalton855d83f2014-09-18 13:51:53 -070092
kkinnunenccdaa042014-08-20 01:36:23 -070093 fTransformType = GrPathRendering::kTranslate_PathTransformType;
kkinnunenc6cb56f2014-06-24 00:12:27 -070094
95 const char* stop = text + byteLength;
96
97 // Measure first if needed.
98 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) {
99 SkFixed stopX = 0;
100 SkFixed stopY = 0;
101
102 const char* textPtr = text;
103 while (textPtr < stop) {
104 // We don't need x, y here, since all subpixel variants will have the
105 // same advance.
cdaltonb85a0aa2014-07-21 15:32:44 -0700106 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &textPtr, 0, 0);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700107
108 stopX += glyph.fAdvanceX;
109 stopY += glyph.fAdvanceY;
110 }
111 SkASSERT(textPtr == stop);
112
113 SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio;
114 SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio;
115
116 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) {
117 alignX = SkScalarHalf(alignX);
118 alignY = SkScalarHalf(alignY);
119 }
120
121 x -= alignX;
122 y -= alignY;
123 }
124
125 SkAutoKern autokern;
126
127 SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio);
128
129 SkFixed fx = SkScalarToFixed(x);
130 SkFixed fy = SkScalarToFixed(y);
131 while (text < stop) {
cdaltonb85a0aa2014-07-21 15:32:44 -0700132 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700133 fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio);
134 if (glyph.fWidth) {
cdaltonb85a0aa2014-07-21 15:32:44 -0700135 this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700136 }
137
138 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio);
139 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio);
140 }
141
142 this->finish();
143}
144
145void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint,
146 const SkPaint& skPaint,
147 const char text[],
148 size_t byteLength,
149 const SkScalar pos[],
fmalita05c4a432014-09-29 06:29:53 -0700150 int scalarsPerPosition,
151 const SkPoint& offset) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700152 SkASSERT(byteLength == 0 || text != NULL);
153 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition);
154
155 // nothing to draw
156 if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) {
157 return;
158 }
159
160 // This is the fast path. Here we do not bake in the device-transform to
161 // the glyph outline or the advances. This is because we do not need to
162 // position the glyphs at all, since the caller has done the positioning.
163 // The positioning is based on SkPaint::measureText of individual
164 // glyphs. That already uses glyph cache without device transforms. Device
165 // transform is not part of SkPaint::measureText API, and thus we use the
166 // same glyphs as what were measured.
kkinnunenc6cb56f2014-06-24 00:12:27 -0700167
fmalita05c4a432014-09-29 06:29:53 -0700168 this->init(paint, skPaint, byteLength, kMaxPerformance_RenderMode, offset);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700169
170 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc();
171
kkinnunenc6cb56f2014-06-24 00:12:27 -0700172 const char* stop = text + byteLength;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700173
174 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) {
cdaltonb2808cd2014-07-25 14:13:57 -0700175 if (1 == scalarsPerPosition) {
kkinnunenccdaa042014-08-20 01:36:23 -0700176 fTransformType = GrPathRendering::kTranslateX_PathTransformType;
cdaltonb2808cd2014-07-25 14:13:57 -0700177 while (text < stop) {
178 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
179 if (glyph.fWidth) {
180 this->appendGlyph(glyph.getGlyphID(), *pos);
181 }
182 pos++;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700183 }
cdaltonb2808cd2014-07-25 14:13:57 -0700184 } else {
185 SkASSERT(2 == scalarsPerPosition);
kkinnunenccdaa042014-08-20 01:36:23 -0700186 fTransformType = GrPathRendering::kTranslate_PathTransformType;
cdaltonb2808cd2014-07-25 14:13:57 -0700187 while (text < stop) {
188 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
189 if (glyph.fWidth) {
190 this->appendGlyph(glyph.getGlyphID(), pos[0], pos[1]);
191 }
192 pos += 2;
193 }
kkinnunenc6cb56f2014-06-24 00:12:27 -0700194 }
195 } else {
kkinnunenccdaa042014-08-20 01:36:23 -0700196 fTransformType = GrPathRendering::kTranslate_PathTransformType;
fmalita05c4a432014-09-29 06:29:53 -0700197 SkTextMapStateProc tmsProc(SkMatrix::I(), SkPoint::Make(0, 0), scalarsPerPosition);
cdaltonb2808cd2014-07-25 14:13:57 -0700198 SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign());
kkinnunenc6cb56f2014-06-24 00:12:27 -0700199 while (text < stop) {
cdaltonb85a0aa2014-07-21 15:32:44 -0700200 const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700201 if (glyph.fWidth) {
202 SkPoint tmsLoc;
203 tmsProc(pos, &tmsLoc);
204 SkPoint loc;
205 alignProc(tmsLoc, glyph, &loc);
206
cdaltonb85a0aa2014-07-21 15:32:44 -0700207 this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y());
kkinnunenc6cb56f2014-06-24 00:12:27 -0700208 }
209 pos += scalarsPerPosition;
210 }
211 }
212
213 this->finish();
214}
215
cdalton855d83f2014-09-18 13:51:53 -0700216static GrPathRange* get_gr_glyphs(GrContext* ctx,
217 const SkTypeface* typeface,
218 const SkDescriptor* desc,
219 const SkStrokeRec& stroke) {
220 static const GrCacheID::Domain gGlyphsDomain = GrCacheID::GenerateDomain();
221
222 GrCacheID::Key key;
223 uint64_t* keyData = key.fData64;
224 keyData[0] = desc ? desc->getChecksum() : 0;
225 keyData[0] = (keyData[0] << 32) | (typeface ? typeface->uniqueID() : 0);
226 keyData[1] = GrPath::ComputeStrokeKey(stroke);
227 GrResourceKey resourceKey = GrResourceKey(GrCacheID(gGlyphsDomain, key),
228 GrPathRange::resourceType(), 0);
229
230 SkAutoTUnref<GrPathRange> glyphs(
231 static_cast<GrPathRange*>(ctx->findAndRefCachedResource(resourceKey)));
232 if (NULL == glyphs || (NULL != desc && !glyphs->isEqualTo(*desc))) {
233 glyphs.reset(ctx->getGpu()->pathRendering()->createGlyphs(typeface, desc, stroke));
234 ctx->addResourceToCache(resourceKey, glyphs);
235 }
236
237 return glyphs.detach();
238}
239
kkinnunenc6cb56f2014-06-24 00:12:27 -0700240void GrStencilAndCoverTextContext::init(const GrPaint& paint,
241 const SkPaint& skPaint,
cdaltonb2808cd2014-07-25 14:13:57 -0700242 size_t textByteLength,
cdalton855d83f2014-09-18 13:51:53 -0700243 RenderMode renderMode,
fmalita05c4a432014-09-29 06:29:53 -0700244 const SkPoint& textTranslate) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700245 GrTextContext::init(paint, skPaint);
246
cdaltonb2808cd2014-07-25 14:13:57 -0700247 fContextInitialMatrix = fContext->getMatrix();
248
cdalton855d83f2014-09-18 13:51:53 -0700249 const bool otherBackendsWillDrawAsPaths =
cdaltonb2808cd2014-07-25 14:13:57 -0700250 SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700251
cdalton855d83f2014-09-18 13:51:53 -0700252 fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths &&
253 kMaxAccuracy_RenderMode == renderMode &&
254 SkToBool(fContextInitialMatrix.getType() &
255 (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700256
257 if (fNeedsDeviceSpaceGlyphs) {
cdalton855d83f2014-09-18 13:51:53 -0700258 // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms.
259 SkASSERT(!fContextInitialMatrix.hasPerspective());
fmalita05c4a432014-09-29 06:29:53 -0700260 SkASSERT(textTranslate.isZero()); // TODO: Handle textTranslate in device-space usecase.
cdaltonb2808cd2014-07-25 14:13:57 -0700261
cdalton855d83f2014-09-18 13:51:53 -0700262 fTextRatio = fTextInverseRatio = 1.0f;
263
264 // Glyphs loaded by GPU path rendering have an inverted y-direction.
265 SkMatrix m;
266 m.setScale(1, -1);
267 fContext->setMatrix(m);
268
269 // Post-flip the initial matrix so we're left with just the flip after
270 // the paint preConcats the inverse.
271 m = fContextInitialMatrix;
272 m.postScale(1, -1);
273 fPaint.localCoordChangeInverse(m);
274
275 // The whole shape (including stroke) will be baked into the glyph outlines. Make
276 // NVPR just fill the baked shapes.
277 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false);
278 fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
279 &fGlyphCache->getDescriptor(),
280 SkStrokeRec(SkStrokeRec::kFill_InitStyle));
kkinnunenc6cb56f2014-06-24 00:12:27 -0700281 } else {
cdalton855d83f2014-09-18 13:51:53 -0700282 // Don't bake strokes into the glyph outlines. We will stroke the glyphs
283 // using the GPU instead. This is the fast path.
284 SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700285 fSkPaint.setStyle(SkPaint::kFill_Style);
cdalton855d83f2014-09-18 13:51:53 -0700286
287 if (gpuStroke.isHairlineStyle()) {
288 // Approximate hairline stroke.
289 SkScalar strokeWidth = SK_Scalar1 /
290 (SkVector::Make(fContextInitialMatrix.getScaleX(),
291 fContextInitialMatrix.getSkewY()).length());
292 gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/);
293
294 } else if (fSkPaint.isFakeBoldText() &&
295#ifdef SK_USE_FREETYPE_EMBOLDEN
296 kMaxPerformance_RenderMode == renderMode &&
297#endif
298 SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) {
299
300 // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke.
301 SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(),
302 kStdFakeBoldInterpKeys,
303 kStdFakeBoldInterpValues,
304 kStdFakeBoldInterpLength);
305 SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale);
306 gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra,
307 true /*strokeAndFill*/);
308
309 fSkPaint.setFakeBoldText(false);
310 }
311
312 bool canUseRawPaths;
313
314 if (otherBackendsWillDrawAsPaths || kMaxPerformance_RenderMode == renderMode) {
315 // We can draw the glyphs from canonically sized paths.
316 fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths;
317 fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize();
318
319 // Compensate for the glyphs being scaled by fTextRatio.
320 if (!gpuStroke.isFillStyle()) {
321 gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio,
322 SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle());
323 }
324
325 fSkPaint.setLinearText(true);
326 fSkPaint.setLCDRenderText(false);
327 fSkPaint.setAutohinted(false);
328 fSkPaint.setHinting(SkPaint::kNo_Hinting);
329 fSkPaint.setSubpixelText(true);
330 fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths));
331
332 canUseRawPaths = SK_Scalar1 == fSkPaint.getTextScaleX() &&
333 0 == fSkPaint.getTextSkewX() &&
334 !fSkPaint.isFakeBoldText() &&
335 !fSkPaint.isVerticalText();
336 } else {
337 fTextRatio = fTextInverseRatio = 1.0f;
338 canUseRawPaths = false;
339 }
340
341 SkMatrix textMatrix;
fmalita05c4a432014-09-29 06:29:53 -0700342 textMatrix.setTranslate(textTranslate.x(), textTranslate.y());
cdalton855d83f2014-09-18 13:51:53 -0700343 // Glyphs loaded by GPU path rendering have an inverted y-direction.
344 textMatrix.preScale(fTextRatio, -fTextRatio);
345 fPaint.localCoordChange(textMatrix);
346 fContext->concatMatrix(textMatrix);
347
348 fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false);
349 fGlyphs = canUseRawPaths ?
350 get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) :
351 get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(),
352 &fGlyphCache->getDescriptor(), gpuStroke);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700353 }
cdalton855d83f2014-09-18 13:51:53 -0700354
kkinnunenc6cb56f2014-06-24 00:12:27 -0700355 fStateRestore.set(fDrawTarget->drawState());
356
357 fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(),
358 fContext->getRenderTarget());
359
360 GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
361 kZero_StencilOp,
362 kZero_StencilOp,
363 kNotEqual_StencilFunc,
364 0xffff,
365 0x0000,
366 0xffff);
367
368 *fDrawTarget->drawState()->stencil() = kStencilPass;
369
cdaltonb85a0aa2014-07-21 15:32:44 -0700370 SkASSERT(0 == fPendingGlyphCount);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700371}
372
cdaltonb2808cd2014-07-25 14:13:57 -0700373inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x) {
kkinnunenccdaa042014-08-20 01:36:23 -0700374 SkASSERT(GrPathRendering::kTranslateX_PathTransformType == fTransformType);
cdaltonb2808cd2014-07-25 14:13:57 -0700375
cdaltonb85a0aa2014-07-21 15:32:44 -0700376 if (fPendingGlyphCount >= kGlyphBufferSize) {
377 this->flush();
378 }
379
cdaltonb85a0aa2014-07-21 15:32:44 -0700380 fIndexBuffer[fPendingGlyphCount] = glyphID;
cdaltonb2808cd2014-07-25 14:13:57 -0700381 fTransformBuffer[fPendingGlyphCount] = fTextInverseRatio * x;
382
383 ++fPendingGlyphCount;
384}
385
386inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) {
kkinnunenccdaa042014-08-20 01:36:23 -0700387 SkASSERT(GrPathRendering::kTranslate_PathTransformType == fTransformType);
cdaltonb2808cd2014-07-25 14:13:57 -0700388
389 if (fPendingGlyphCount >= kGlyphBufferSize) {
390 this->flush();
391 }
392
cdaltonb2808cd2014-07-25 14:13:57 -0700393 fIndexBuffer[fPendingGlyphCount] = glyphID;
394 fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x;
cdalton855d83f2014-09-18 13:51:53 -0700395 fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y;
cdaltonb85a0aa2014-07-21 15:32:44 -0700396
397 ++fPendingGlyphCount;
398}
399
400void GrStencilAndCoverTextContext::flush() {
401 if (0 == fPendingGlyphCount) {
kkinnunenc6cb56f2014-06-24 00:12:27 -0700402 return;
403 }
404
cdalton855d83f2014-09-18 13:51:53 -0700405 fDrawTarget->drawPaths(fGlyphs, fIndexBuffer, fPendingGlyphCount,
cdaltonb2808cd2014-07-25 14:13:57 -0700406 fTransformBuffer, fTransformType, SkPath::kWinding_FillType);
cdaltonb85a0aa2014-07-21 15:32:44 -0700407
408 fPendingGlyphCount = 0;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700409}
410
411void GrStencilAndCoverTextContext::finish() {
cdaltonb85a0aa2014-07-21 15:32:44 -0700412 this->flush();
kkinnunenc6cb56f2014-06-24 00:12:27 -0700413
cdalton855d83f2014-09-18 13:51:53 -0700414 fGlyphs->unref();
cdaltonb85a0aa2014-07-21 15:32:44 -0700415 fGlyphs = NULL;
cdalton855d83f2014-09-18 13:51:53 -0700416
417 SkGlyphCache::AttachCache(fGlyphCache);
cdaltonb85a0aa2014-07-21 15:32:44 -0700418 fGlyphCache = NULL;
kkinnunenc6cb56f2014-06-24 00:12:27 -0700419
420 fDrawTarget->drawState()->stencil()->setDisabled();
421 fStateRestore.set(NULL);
cdaltonb2808cd2014-07-25 14:13:57 -0700422 fContext->setMatrix(fContextInitialMatrix);
kkinnunenc6cb56f2014-06-24 00:12:27 -0700423 GrTextContext::finish();
424}
425