OLD | NEW |
(Empty) | |
| 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 "GrFontScaler.h" |
| 11 #include "GrGpu.h" |
| 12 #include "GrPath.h" |
| 13 #include "GrTextStrike.h" |
| 14 #include "GrTextStrike_impl.h" |
| 15 #include "SkAutoKern.h" |
| 16 #include "SkDraw.h" |
| 17 #include "SkDrawProcs.h" |
| 18 #include "SkGlyphCache.h" |
| 19 #include "SkGpuDevice.h" |
| 20 #include "SkPath.h" |
| 21 #include "SkTextMapStateProc.h" |
| 22 |
| 23 static const int kMaxReservedGlyphs = 64; |
| 24 |
| 25 GrStencilAndCoverTextContext::GrStencilAndCoverTextContext( |
| 26 GrContext* context, const SkDeviceProperties& properties) |
| 27 : GrTextContext(context, properties) |
| 28 , fStroke(SkStrokeRec::kFill_InitStyle) { |
| 29 } |
| 30 |
| 31 GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() { |
| 32 } |
| 33 |
| 34 void GrStencilAndCoverTextContext::drawText(const GrPaint& paint, |
| 35 const SkPaint& skPaint, |
| 36 const char text[], |
| 37 size_t byteLength, |
| 38 SkScalar x, SkScalar y) { |
| 39 SkASSERT(byteLength == 0 || text != NULL); |
| 40 |
| 41 if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) { |
| 42 return; |
| 43 } |
| 44 |
| 45 // This is the slow path, mainly used by Skia unit tests. The other |
| 46 // backends (8888, gpu, ...) use device-space dependent glyph caches. In |
| 47 // order to match the glyph positions that the other code paths produce, we |
| 48 // must also use device-space dependent glyph cache. This has the |
| 49 // side-effect that the glyph shape outline will be in device-space, |
| 50 // too. This in turn has the side-effect that NVPR can not stroke the paths, |
| 51 // as the stroke in NVPR is defined in object-space. |
| 52 // NOTE: here we have following coincidence that works at the moment: |
| 53 // - When using the device-space glyphs, the transforms we pass to NVPR |
| 54 // instanced drawing are the global transforms, and the view transform is |
| 55 // identity. NVPR can not use non-affine transforms in the instanced |
| 56 // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it |
| 57 // will turn off the use of device-space glyphs when perspective transforms |
| 58 // are in use. |
| 59 |
| 60 fGlyphTransform = fContext->getMatrix(); |
| 61 |
| 62 this->init(paint, skPaint, byteLength); |
| 63 |
| 64 SkMatrix* glyphCacheTransform = NULL; |
| 65 // Transform our starting point. |
| 66 if (fNeedsDeviceSpaceGlyphs) { |
| 67 SkPoint loc; |
| 68 fGlyphTransform.mapXY(x, y, &loc); |
| 69 x = loc.fX; |
| 70 y = loc.fY; |
| 71 glyphCacheTransform = &fGlyphTransform; |
| 72 } |
| 73 |
| 74 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
| 75 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform
); |
| 76 SkGlyphCache* cache = autoCache.getCache(); |
| 77 GrFontScaler* scaler = GetGrFontScaler(cache); |
| 78 GrTextStrike* strike = |
| 79 fContext->getFontCache()->getStrike(scaler, true); |
| 80 |
| 81 const char* stop = text + byteLength; |
| 82 |
| 83 // Measure first if needed. |
| 84 if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { |
| 85 SkFixed stopX = 0; |
| 86 SkFixed stopY = 0; |
| 87 |
| 88 const char* textPtr = text; |
| 89 while (textPtr < stop) { |
| 90 // We don't need x, y here, since all subpixel variants will have th
e |
| 91 // same advance. |
| 92 const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0); |
| 93 |
| 94 stopX += glyph.fAdvanceX; |
| 95 stopY += glyph.fAdvanceY; |
| 96 } |
| 97 SkASSERT(textPtr == stop); |
| 98 |
| 99 SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio; |
| 100 SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio; |
| 101 |
| 102 if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { |
| 103 alignX = SkScalarHalf(alignX); |
| 104 alignY = SkScalarHalf(alignY); |
| 105 } |
| 106 |
| 107 x -= alignX; |
| 108 y -= alignY; |
| 109 } |
| 110 |
| 111 SkAutoKern autokern; |
| 112 |
| 113 SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio); |
| 114 |
| 115 SkFixed fx = SkScalarToFixed(x); |
| 116 SkFixed fy = SkScalarToFixed(y); |
| 117 while (text < stop) { |
| 118 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| 119 fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio); |
| 120 if (glyph.fWidth) { |
| 121 this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
| 122 glyph.getSubXFixed(), |
| 123 glyph.getSubYFixed()), |
| 124 SkPoint::Make( |
| 125 SkFixedToScalar(fx), |
| 126 SkFixedToScalar(fy)), |
| 127 strike, |
| 128 scaler); |
| 129 } |
| 130 |
| 131 fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio); |
| 132 fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio); |
| 133 } |
| 134 |
| 135 this->finish(); |
| 136 } |
| 137 |
| 138 void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint, |
| 139 const SkPaint& skPaint, |
| 140 const char text[], |
| 141 size_t byteLength, |
| 142 const SkScalar pos[], |
| 143 SkScalar constY, |
| 144 int scalarsPerPosition) { |
| 145 SkASSERT(byteLength == 0 || text != NULL); |
| 146 SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); |
| 147 |
| 148 // nothing to draw |
| 149 if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) { |
| 150 return; |
| 151 } |
| 152 |
| 153 // This is the fast path. Here we do not bake in the device-transform to |
| 154 // the glyph outline or the advances. This is because we do not need to |
| 155 // position the glyphs at all, since the caller has done the positioning. |
| 156 // The positioning is based on SkPaint::measureText of individual |
| 157 // glyphs. That already uses glyph cache without device transforms. Device |
| 158 // transform is not part of SkPaint::measureText API, and thus we use the |
| 159 // same glyphs as what were measured. |
| 160 fGlyphTransform.reset(); |
| 161 |
| 162 this->init(paint, skPaint, byteLength); |
| 163 |
| 164 SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
| 165 |
| 166 SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL); |
| 167 SkGlyphCache* cache = autoCache.getCache(); |
| 168 GrFontScaler* scaler = GetGrFontScaler(cache); |
| 169 GrTextStrike* strike = |
| 170 fContext->getFontCache()->getStrike(scaler, true); |
| 171 |
| 172 const char* stop = text + byteLength; |
| 173 SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign()); |
| 174 SkTextMapStateProc tmsProc(SkMatrix::I(), constY, scalarsPerPosition); |
| 175 |
| 176 if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
| 177 while (text < stop) { |
| 178 SkPoint loc; |
| 179 tmsProc(pos, &loc); |
| 180 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| 181 if (glyph.fWidth) { |
| 182 this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
| 183 glyph.getSubXFixed(), |
| 184 glyph.getSubYFixed()), |
| 185 loc, |
| 186 strike, |
| 187 scaler); |
| 188 } |
| 189 pos += scalarsPerPosition; |
| 190 } |
| 191 } else { |
| 192 while (text < stop) { |
| 193 const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| 194 if (glyph.fWidth) { |
| 195 SkPoint tmsLoc; |
| 196 tmsProc(pos, &tmsLoc); |
| 197 SkPoint loc; |
| 198 alignProc(tmsLoc, glyph, &loc); |
| 199 |
| 200 this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
| 201 glyph.getSubXFixed(), |
| 202 glyph.getSubYFixed()), |
| 203 loc, |
| 204 strike, |
| 205 scaler); |
| 206 |
| 207 } |
| 208 pos += scalarsPerPosition; |
| 209 } |
| 210 } |
| 211 |
| 212 this->finish(); |
| 213 } |
| 214 |
| 215 bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) { |
| 216 if (paint.getRasterizer()) { |
| 217 return false; |
| 218 } |
| 219 if (paint.getMaskFilter()) { |
| 220 return false; |
| 221 } |
| 222 if (paint.getPathEffect()) { |
| 223 return false; |
| 224 } |
| 225 |
| 226 // No hairlines unless we can map the 1 px width to the object space. |
| 227 if (paint.getStyle() == SkPaint::kStroke_Style |
| 228 && paint.getStrokeWidth() == 0 |
| 229 && fContext->getMatrix().hasPerspective()) { |
| 230 return false; |
| 231 } |
| 232 |
| 233 // No color bitmap fonts. |
| 234 SkScalerContext::Rec rec; |
| 235 SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec); |
| 236 return rec.getFormat() != SkMask::kARGB32_Format; |
| 237 } |
| 238 |
| 239 void GrStencilAndCoverTextContext::init(const GrPaint& paint, |
| 240 const SkPaint& skPaint, |
| 241 size_t textByteLength) { |
| 242 GrTextContext::init(paint, skPaint); |
| 243 |
| 244 bool otherBackendsWillDrawAsPaths = |
| 245 SkDraw::ShouldDrawTextAsPaths(skPaint, fContext->getMatrix()); |
| 246 |
| 247 if (otherBackendsWillDrawAsPaths) { |
| 248 // This is to reproduce SkDraw::drawText_asPaths glyph positions. |
| 249 fSkPaint.setLinearText(true); |
| 250 fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPath
s; |
| 251 fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths))
; |
| 252 if (fSkPaint.getStyle() != SkPaint::kFill_Style) { |
| 253 // Compensate the glyphs being scaled up by fTextRatio by scaling th
e |
| 254 // stroke down. |
| 255 fSkPaint.setStrokeWidth(fSkPaint.getStrokeWidth() / fTextRatio); |
| 256 } |
| 257 fNeedsDeviceSpaceGlyphs = false; |
| 258 } else { |
| 259 fTextRatio = 1.0f; |
| 260 fNeedsDeviceSpaceGlyphs = (fGlyphTransform.getType() & |
| 261 (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0; |
| 262 // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms. |
| 263 SkASSERT(!fGlyphTransform.hasPerspective()); |
| 264 if (fNeedsDeviceSpaceGlyphs) { |
| 265 fPaint.localCoordChangeInverse(fGlyphTransform); |
| 266 fContext->setIdentityMatrix(); |
| 267 } |
| 268 } |
| 269 |
| 270 fStroke = SkStrokeRec(fSkPaint); |
| 271 |
| 272 if (fNeedsDeviceSpaceGlyphs) { |
| 273 // The whole shape is baked into the glyph. Make NVPR just fill the |
| 274 // baked shape. |
| 275 fStroke.setStrokeStyle(-1, false); |
| 276 } else { |
| 277 if (fSkPaint.getStrokeWidth() == 0.0f) { |
| 278 if (fSkPaint.getStyle() == SkPaint::kStrokeAndFill_Style) { |
| 279 fStroke.setStrokeStyle(-1, false); |
| 280 } else if (fSkPaint.getStyle() == SkPaint::kStroke_Style) { |
| 281 // Approximate hairline stroke. |
| 282 const SkMatrix& ctm = fContext->getMatrix(); |
| 283 SkScalar strokeWidth = SK_Scalar1 / |
| 284 (fTextRatio * SkVector::Make(ctm.getScaleX(), ctm.getSkewY()
).length()); |
| 285 fStroke.setStrokeStyle(strokeWidth, false); |
| 286 } |
| 287 } |
| 288 |
| 289 // Make glyph cache produce paths geometry for fill. We will stroke them |
| 290 // by passing fStroke to drawPath. This is the fast path. |
| 291 fSkPaint.setStyle(SkPaint::kFill_Style); |
| 292 } |
| 293 fStateRestore.set(fDrawTarget->drawState()); |
| 294 |
| 295 fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(), |
| 296 fContext->getRenderTarget()); |
| 297 |
| 298 GR_STATIC_CONST_SAME_STENCIL(kStencilPass, |
| 299 kZero_StencilOp, |
| 300 kZero_StencilOp, |
| 301 kNotEqual_StencilFunc, |
| 302 0xffff, |
| 303 0x0000, |
| 304 0xffff); |
| 305 |
| 306 *fDrawTarget->drawState()->stencil() = kStencilPass; |
| 307 |
| 308 size_t reserveAmount; |
| 309 switch (skPaint.getTextEncoding()) { |
| 310 default: |
| 311 SkASSERT(false); |
| 312 case SkPaint::kUTF8_TextEncoding: |
| 313 reserveAmount = textByteLength; |
| 314 break; |
| 315 case SkPaint::kUTF16_TextEncoding: |
| 316 reserveAmount = textByteLength / 2; |
| 317 break; |
| 318 case SkPaint::kUTF32_TextEncoding: |
| 319 case SkPaint::kGlyphID_TextEncoding: |
| 320 reserveAmount = textByteLength / 4; |
| 321 break; |
| 322 } |
| 323 fPaths.setReserve(reserveAmount); |
| 324 fTransforms.setReserve(reserveAmount); |
| 325 } |
| 326 |
| 327 inline void GrStencilAndCoverTextContext::appendGlyph(GrGlyph::PackedID glyphID, |
| 328 const SkPoint& pos, |
| 329 GrTextStrike* strike, |
| 330 GrFontScaler* scaler) { |
| 331 GrGlyph* glyph = strike->getGlyph(glyphID, scaler); |
| 332 if (NULL == glyph || glyph->fBounds.isEmpty()) { |
| 333 return; |
| 334 } |
| 335 |
| 336 if (scaler->getGlyphPath(glyph->glyphID(), &fTmpPath)) { |
| 337 if (!fTmpPath.isEmpty()) { |
| 338 *fPaths.append() = fContext->createPath(fTmpPath, fStroke); |
| 339 SkMatrix* t = fTransforms.append(); |
| 340 t->setTranslate(pos.fX, pos.fY); |
| 341 t->preScale(fTextRatio, fTextRatio); |
| 342 } |
| 343 } |
| 344 } |
| 345 |
| 346 void GrStencilAndCoverTextContext::finish() { |
| 347 if (fPaths.count() > 0) { |
| 348 fDrawTarget->drawPaths(static_cast<size_t>(fPaths.count()), |
| 349 fPaths.begin(), fTransforms.begin(), |
| 350 SkPath::kWinding_FillType, fStroke.getStyle()); |
| 351 |
| 352 for (int i = 0; i < fPaths.count(); ++i) { |
| 353 fPaths[i]->unref(); |
| 354 } |
| 355 if (fPaths.count() > kMaxReservedGlyphs) { |
| 356 fPaths.reset(); |
| 357 fTransforms.reset(); |
| 358 } else { |
| 359 fPaths.rewind(); |
| 360 fTransforms.rewind(); |
| 361 } |
| 362 } |
| 363 fTmpPath.reset(); |
| 364 |
| 365 fDrawTarget->drawState()->stencil()->setDisabled(); |
| 366 fStateRestore.set(NULL); |
| 367 if (fNeedsDeviceSpaceGlyphs) { |
| 368 fContext->setMatrix(fGlyphTransform); |
| 369 } |
| 370 GrTextContext::finish(); |
| 371 } |
| 372 |
OLD | NEW |