Index: src/gpu/GrStencilAndCoverTextContext.cpp |
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp |
new file mode 100644 |
index 0000000000000000000000000000000000000000..637c85a997d8942da5c725777b8bef544fcf0227 |
--- /dev/null |
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp |
@@ -0,0 +1,372 @@ |
+/* |
+ * Copyright 2014 Google Inc. |
+ * |
+ * Use of this source code is governed by a BSD-style license that can be |
+ * found in the LICENSE file. |
+ */ |
+ |
+#include "GrStencilAndCoverTextContext.h" |
+#include "GrDrawTarget.h" |
+#include "GrFontScaler.h" |
+#include "GrGpu.h" |
+#include "GrPath.h" |
+#include "GrTextStrike.h" |
+#include "GrTextStrike_impl.h" |
+#include "SkAutoKern.h" |
+#include "SkDraw.h" |
+#include "SkDrawProcs.h" |
+#include "SkGlyphCache.h" |
+#include "SkGpuDevice.h" |
+#include "SkPath.h" |
+#include "SkTextMapStateProc.h" |
+ |
+static const int kMaxReservedGlyphs = 64; |
+ |
+GrStencilAndCoverTextContext::GrStencilAndCoverTextContext( |
+ GrContext* context, const SkDeviceProperties& properties) |
+ : GrTextContext(context, properties) |
+ , fStroke(SkStrokeRec::kFill_InitStyle) { |
+} |
+ |
+GrStencilAndCoverTextContext::~GrStencilAndCoverTextContext() { |
+} |
+ |
+void GrStencilAndCoverTextContext::drawText(const GrPaint& paint, |
+ const SkPaint& skPaint, |
+ const char text[], |
+ size_t byteLength, |
+ SkScalar x, SkScalar y) { |
+ SkASSERT(byteLength == 0 || text != NULL); |
+ |
+ if (text == NULL || byteLength == 0 /*|| fRC->isEmpty()*/) { |
+ return; |
+ } |
+ |
+ // This is the slow path, mainly used by Skia unit tests. The other |
+ // backends (8888, gpu, ...) use device-space dependent glyph caches. In |
+ // order to match the glyph positions that the other code paths produce, we |
+ // must also use device-space dependent glyph cache. This has the |
+ // side-effect that the glyph shape outline will be in device-space, |
+ // too. This in turn has the side-effect that NVPR can not stroke the paths, |
+ // as the stroke in NVPR is defined in object-space. |
+ // NOTE: here we have following coincidence that works at the moment: |
+ // - When using the device-space glyphs, the transforms we pass to NVPR |
+ // instanced drawing are the global transforms, and the view transform is |
+ // identity. NVPR can not use non-affine transforms in the instanced |
+ // drawing. This is taken care of by SkDraw::ShouldDrawTextAsPaths since it |
+ // will turn off the use of device-space glyphs when perspective transforms |
+ // are in use. |
+ |
+ fGlyphTransform = fContext->getMatrix(); |
+ |
+ this->init(paint, skPaint, byteLength); |
+ |
+ SkMatrix* glyphCacheTransform = NULL; |
+ // Transform our starting point. |
+ if (fNeedsDeviceSpaceGlyphs) { |
+ SkPoint loc; |
+ fGlyphTransform.mapXY(x, y, &loc); |
+ x = loc.fX; |
+ y = loc.fY; |
+ glyphCacheTransform = &fGlyphTransform; |
+ } |
+ |
+ SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
+ SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, glyphCacheTransform); |
+ SkGlyphCache* cache = autoCache.getCache(); |
+ GrFontScaler* scaler = GetGrFontScaler(cache); |
+ GrTextStrike* strike = |
+ fContext->getFontCache()->getStrike(scaler, true); |
+ |
+ const char* stop = text + byteLength; |
+ |
+ // Measure first if needed. |
+ if (fSkPaint.getTextAlign() != SkPaint::kLeft_Align) { |
+ SkFixed stopX = 0; |
+ SkFixed stopY = 0; |
+ |
+ const char* textPtr = text; |
+ while (textPtr < stop) { |
+ // We don't need x, y here, since all subpixel variants will have the |
+ // same advance. |
+ const SkGlyph& glyph = glyphCacheProc(cache, &textPtr, 0, 0); |
+ |
+ stopX += glyph.fAdvanceX; |
+ stopY += glyph.fAdvanceY; |
+ } |
+ SkASSERT(textPtr == stop); |
+ |
+ SkScalar alignX = SkFixedToScalar(stopX) * fTextRatio; |
+ SkScalar alignY = SkFixedToScalar(stopY) * fTextRatio; |
+ |
+ if (fSkPaint.getTextAlign() == SkPaint::kCenter_Align) { |
+ alignX = SkScalarHalf(alignX); |
+ alignY = SkScalarHalf(alignY); |
+ } |
+ |
+ x -= alignX; |
+ y -= alignY; |
+ } |
+ |
+ SkAutoKern autokern; |
+ |
+ SkFixed fixedSizeRatio = SkScalarToFixed(fTextRatio); |
+ |
+ SkFixed fx = SkScalarToFixed(x); |
+ SkFixed fy = SkScalarToFixed(y); |
+ while (text < stop) { |
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
+ fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio); |
+ if (glyph.fWidth) { |
+ this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
+ glyph.getSubXFixed(), |
+ glyph.getSubYFixed()), |
+ SkPoint::Make( |
+ SkFixedToScalar(fx), |
+ SkFixedToScalar(fy)), |
+ strike, |
+ scaler); |
+ } |
+ |
+ fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio); |
+ fy += SkFixedMul_portable(glyph.fAdvanceY, fixedSizeRatio); |
+ } |
+ |
+ this->finish(); |
+} |
+ |
+void GrStencilAndCoverTextContext::drawPosText(const GrPaint& paint, |
+ const SkPaint& skPaint, |
+ const char text[], |
+ size_t byteLength, |
+ const SkScalar pos[], |
+ SkScalar constY, |
+ int scalarsPerPosition) { |
+ SkASSERT(byteLength == 0 || text != NULL); |
+ SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); |
+ |
+ // nothing to draw |
+ if (text == NULL || byteLength == 0/* || fRC->isEmpty()*/) { |
+ return; |
+ } |
+ |
+ // This is the fast path. Here we do not bake in the device-transform to |
+ // the glyph outline or the advances. This is because we do not need to |
+ // position the glyphs at all, since the caller has done the positioning. |
+ // The positioning is based on SkPaint::measureText of individual |
+ // glyphs. That already uses glyph cache without device transforms. Device |
+ // transform is not part of SkPaint::measureText API, and thus we use the |
+ // same glyphs as what were measured. |
+ fGlyphTransform.reset(); |
+ |
+ this->init(paint, skPaint, byteLength); |
+ |
+ SkDrawCacheProc glyphCacheProc = fSkPaint.getDrawCacheProc(); |
+ |
+ SkAutoGlyphCache autoCache(fSkPaint, &fDeviceProperties, NULL); |
+ SkGlyphCache* cache = autoCache.getCache(); |
+ GrFontScaler* scaler = GetGrFontScaler(cache); |
+ GrTextStrike* strike = |
+ fContext->getFontCache()->getStrike(scaler, true); |
+ |
+ const char* stop = text + byteLength; |
+ SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign()); |
+ SkTextMapStateProc tmsProc(SkMatrix::I(), constY, scalarsPerPosition); |
+ |
+ if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
+ while (text < stop) { |
+ SkPoint loc; |
+ tmsProc(pos, &loc); |
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
+ if (glyph.fWidth) { |
+ this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
+ glyph.getSubXFixed(), |
+ glyph.getSubYFixed()), |
+ loc, |
+ strike, |
+ scaler); |
+ } |
+ pos += scalarsPerPosition; |
+ } |
+ } else { |
+ while (text < stop) { |
+ const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
+ if (glyph.fWidth) { |
+ SkPoint tmsLoc; |
+ tmsProc(pos, &tmsLoc); |
+ SkPoint loc; |
+ alignProc(tmsLoc, glyph, &loc); |
+ |
+ this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
+ glyph.getSubXFixed(), |
+ glyph.getSubYFixed()), |
+ loc, |
+ strike, |
+ scaler); |
+ |
+ } |
+ pos += scalarsPerPosition; |
+ } |
+ } |
+ |
+ this->finish(); |
+} |
+ |
+bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) { |
+ if (paint.getRasterizer()) { |
+ return false; |
+ } |
+ if (paint.getMaskFilter()) { |
+ return false; |
+ } |
+ if (paint.getPathEffect()) { |
+ return false; |
+ } |
+ |
+ // No hairlines unless we can map the 1 px width to the object space. |
+ if (paint.getStyle() == SkPaint::kStroke_Style |
+ && paint.getStrokeWidth() == 0 |
+ && fContext->getMatrix().hasPerspective()) { |
+ return false; |
+ } |
+ |
+ // No color bitmap fonts. |
+ SkScalerContext::Rec rec; |
+ SkScalerContext::MakeRec(paint, &fDeviceProperties, NULL, &rec); |
+ return rec.getFormat() != SkMask::kARGB32_Format; |
+} |
+ |
+void GrStencilAndCoverTextContext::init(const GrPaint& paint, |
+ const SkPaint& skPaint, |
+ size_t textByteLength) { |
+ GrTextContext::init(paint, skPaint); |
+ |
+ bool otherBackendsWillDrawAsPaths = |
+ SkDraw::ShouldDrawTextAsPaths(skPaint, fContext->getMatrix()); |
+ |
+ if (otherBackendsWillDrawAsPaths) { |
+ // This is to reproduce SkDraw::drawText_asPaths glyph positions. |
+ fSkPaint.setLinearText(true); |
+ fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; |
+ fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); |
+ if (fSkPaint.getStyle() != SkPaint::kFill_Style) { |
+ // Compensate the glyphs being scaled up by fTextRatio by scaling the |
+ // stroke down. |
+ fSkPaint.setStrokeWidth(fSkPaint.getStrokeWidth() / fTextRatio); |
+ } |
+ fNeedsDeviceSpaceGlyphs = false; |
+ } else { |
+ fTextRatio = 1.0f; |
+ fNeedsDeviceSpaceGlyphs = (fGlyphTransform.getType() & |
+ (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)) != 0; |
+ // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms. |
+ SkASSERT(!fGlyphTransform.hasPerspective()); |
+ if (fNeedsDeviceSpaceGlyphs) { |
+ fPaint.localCoordChangeInverse(fGlyphTransform); |
+ fContext->setIdentityMatrix(); |
+ } |
+ } |
+ |
+ fStroke = SkStrokeRec(fSkPaint); |
+ |
+ if (fNeedsDeviceSpaceGlyphs) { |
+ // The whole shape is baked into the glyph. Make NVPR just fill the |
+ // baked shape. |
+ fStroke.setStrokeStyle(-1, false); |
+ } else { |
+ if (fSkPaint.getStrokeWidth() == 0.0f) { |
+ if (fSkPaint.getStyle() == SkPaint::kStrokeAndFill_Style) { |
+ fStroke.setStrokeStyle(-1, false); |
+ } else if (fSkPaint.getStyle() == SkPaint::kStroke_Style) { |
+ // Approximate hairline stroke. |
+ const SkMatrix& ctm = fContext->getMatrix(); |
+ SkScalar strokeWidth = SK_Scalar1 / |
+ (fTextRatio * SkVector::Make(ctm.getScaleX(), ctm.getSkewY()).length()); |
+ fStroke.setStrokeStyle(strokeWidth, false); |
+ } |
+ } |
+ |
+ // Make glyph cache produce paths geometry for fill. We will stroke them |
+ // by passing fStroke to drawPath. This is the fast path. |
+ fSkPaint.setStyle(SkPaint::kFill_Style); |
+ } |
+ fStateRestore.set(fDrawTarget->drawState()); |
+ |
+ fDrawTarget->drawState()->setFromPaint(fPaint, fContext->getMatrix(), |
+ fContext->getRenderTarget()); |
+ |
+ GR_STATIC_CONST_SAME_STENCIL(kStencilPass, |
+ kZero_StencilOp, |
+ kZero_StencilOp, |
+ kNotEqual_StencilFunc, |
+ 0xffff, |
+ 0x0000, |
+ 0xffff); |
+ |
+ *fDrawTarget->drawState()->stencil() = kStencilPass; |
+ |
+ size_t reserveAmount; |
+ switch (skPaint.getTextEncoding()) { |
+ default: |
+ SkASSERT(false); |
+ case SkPaint::kUTF8_TextEncoding: |
+ reserveAmount = textByteLength; |
+ break; |
+ case SkPaint::kUTF16_TextEncoding: |
+ reserveAmount = textByteLength / 2; |
+ break; |
+ case SkPaint::kUTF32_TextEncoding: |
+ case SkPaint::kGlyphID_TextEncoding: |
+ reserveAmount = textByteLength / 4; |
+ break; |
+ } |
+ fPaths.setReserve(reserveAmount); |
+ fTransforms.setReserve(reserveAmount); |
+} |
+ |
+inline void GrStencilAndCoverTextContext::appendGlyph(GrGlyph::PackedID glyphID, |
+ const SkPoint& pos, |
+ GrTextStrike* strike, |
+ GrFontScaler* scaler) { |
+ GrGlyph* glyph = strike->getGlyph(glyphID, scaler); |
+ if (NULL == glyph || glyph->fBounds.isEmpty()) { |
+ return; |
+ } |
+ |
+ if (scaler->getGlyphPath(glyph->glyphID(), &fTmpPath)) { |
+ if (!fTmpPath.isEmpty()) { |
+ *fPaths.append() = fContext->createPath(fTmpPath, fStroke); |
+ SkMatrix* t = fTransforms.append(); |
+ t->setTranslate(pos.fX, pos.fY); |
+ t->preScale(fTextRatio, fTextRatio); |
+ } |
+ } |
+} |
+ |
+void GrStencilAndCoverTextContext::finish() { |
+ if (fPaths.count() > 0) { |
+ fDrawTarget->drawPaths(static_cast<size_t>(fPaths.count()), |
+ fPaths.begin(), fTransforms.begin(), |
+ SkPath::kWinding_FillType, fStroke.getStyle()); |
+ |
+ for (int i = 0; i < fPaths.count(); ++i) { |
+ fPaths[i]->unref(); |
+ } |
+ if (fPaths.count() > kMaxReservedGlyphs) { |
+ fPaths.reset(); |
+ fTransforms.reset(); |
+ } else { |
+ fPaths.rewind(); |
+ fTransforms.rewind(); |
+ } |
+ } |
+ fTmpPath.reset(); |
+ |
+ fDrawTarget->drawState()->stencil()->setDisabled(); |
+ fStateRestore.set(NULL); |
+ if (fNeedsDeviceSpaceGlyphs) { |
+ fContext->setMatrix(fGlyphTransform); |
+ } |
+ GrTextContext::finish(); |
+} |
+ |