diff --git a/include/core/SkStrokeRec.h b/include/core/SkStrokeRec.h index 0c5892f625..68fa14581d 100644 --- a/include/core/SkStrokeRec.h +++ b/include/core/SkStrokeRec.h @@ -85,6 +85,11 @@ public: */ bool applyToPath(SkPath* dst, const SkPath& src) const; + /** + * Apply these stroke parameters to a paint. + */ + void applyToPaint(SkPaint* paint) const; + bool operator==(const SkStrokeRec& other) const { return fWidth == other.fWidth && fMiterLimit == other.fMiterLimit && diff --git a/src/core/SkStrokeRec.cpp b/src/core/SkStrokeRec.cpp index 22f0562fe2..95f56b76a6 100644 --- a/src/core/SkStrokeRec.cpp +++ b/src/core/SkStrokeRec.cpp @@ -114,3 +114,16 @@ bool SkStrokeRec::applyToPath(SkPath* dst, const SkPath& src) const { stroker.strokePath(src, dst); return true; } + +void SkStrokeRec::applyToPaint(SkPaint* paint) const { + if (fWidth < 0) { // fill + paint->setStyle(SkPaint::kFill_Style); + return; + } + + paint->setStyle(fStrokeAndFill ? SkPaint::kStrokeAndFill_Style : SkPaint::kStroke_Style); + paint->setStrokeWidth(fWidth); + paint->setStrokeMiter(fMiterLimit); + paint->setStrokeCap(fCap); + paint->setStrokeJoin(fJoin); +} diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp index d6efe9810c..d33bc7a8bf 100644 --- a/src/gpu/GrStencilAndCoverTextContext.cpp +++ b/src/gpu/GrStencilAndCoverTextContext.cpp @@ -23,7 +23,9 @@ GrStencilAndCoverTextContext::GrStencilAndCoverTextContext( GrContext* context, const SkDeviceProperties& properties) : GrTextContext(context, properties) - , fPendingGlyphCount(0) { + , fStroke(SkStrokeRec::kFill_InitStyle) + , fQueuedGlyphCount(0) + , fFallbackGlyphsIdx(kGlyphBufferSize) { } GrStencilAndCoverTextContext* GrStencilAndCoverTextContext::Create(GrContext* context, @@ -63,10 +65,10 @@ bool GrStencilAndCoverTextContext::canDraw(const SkPaint& paint) { } void GrStencilAndCoverTextContext::onDrawText(const GrPaint& paint, - const SkPaint& skPaint, - const char text[], - size_t byteLength, - SkScalar x, SkScalar y) { + 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()*/) { @@ -91,7 +93,7 @@ void GrStencilAndCoverTextContext::onDrawText(const GrPaint& paint, this->init(paint, skPaint, byteLength, kMaxAccuracy_RenderMode); // Transform our starting point. - if (fNeedsDeviceSpaceGlyphs) { + if (fUsingDeviceSpaceGlyphs) { SkPoint loc; fContextInitialMatrix.mapXY(x, y, &loc); x = loc.fX; @@ -140,7 +142,7 @@ void GrStencilAndCoverTextContext::onDrawText(const GrPaint& paint, const SkGlyph& glyph = glyphCacheProc(fGlyphCache, &text, 0, 0); fx += SkFixedMul_portable(autokern.adjust(glyph), fixedSizeRatio); if (glyph.fWidth) { - this->appendGlyph(glyph.getGlyphID(), SkFixedToScalar(fx), SkFixedToScalar(fy)); + this->appendGlyph(glyph, SkPoint::Make(SkFixedToScalar(fx), SkFixedToScalar(fy))); } fx += SkFixedMul_portable(glyph.fAdvanceX, fixedSizeRatio); @@ -151,12 +153,12 @@ void GrStencilAndCoverTextContext::onDrawText(const GrPaint& paint, } void GrStencilAndCoverTextContext::onDrawPosText(const GrPaint& paint, - const SkPaint& skPaint, - const char text[], - size_t byteLength, - const SkScalar pos[], - int scalarsPerPosition, - const SkPoint& offset) { + const SkPaint& skPaint, + const char text[], + size_t byteLength, + const SkScalar pos[], + int scalarsPerPosition, + const SkPoint& offset) { SkASSERT(byteLength == 0 || text != NULL); SkASSERT(1 == scalarsPerPosition || 2 == scalarsPerPosition); @@ -189,7 +191,7 @@ void GrStencilAndCoverTextContext::onDrawPosText(const GrPaint& paint, SkPoint loc; alignProc(tmsLoc, glyph, &loc); - this->appendGlyph(glyph.getGlyphID(), loc.x(), loc.y()); + this->appendGlyph(glyph, loc); } pos += scalarsPerPosition; } @@ -232,15 +234,19 @@ void GrStencilAndCoverTextContext::init(const GrPaint& paint, const bool otherBackendsWillDrawAsPaths = SkDraw::ShouldDrawTextAsPaths(skPaint, fContextInitialMatrix); - fNeedsDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths && + fUsingDeviceSpaceGlyphs = !otherBackendsWillDrawAsPaths && kMaxAccuracy_RenderMode == renderMode && SkToBool(fContextInitialMatrix.getType() & (SkMatrix::kScale_Mask | SkMatrix::kAffine_Mask)); - if (fNeedsDeviceSpaceGlyphs) { + if (fUsingDeviceSpaceGlyphs) { // SkDraw::ShouldDrawTextAsPaths takes care of perspective transforms. SkASSERT(!fContextInitialMatrix.hasPerspective()); + // The whole shape (including stroke) will be baked into the glyph outlines. Make + // NVPR just fill the baked shapes. + fStroke = SkStrokeRec(SkStrokeRec::kFill_InitStyle); + fTextRatio = fTextInverseRatio = 1.0f; // Glyphs loaded by GPU path rendering have an inverted y-direction. @@ -254,30 +260,27 @@ void GrStencilAndCoverTextContext::init(const GrPaint& paint, m.postScale(1, -1); fPaint.localCoordChangeInverse(m); - // The whole shape (including stroke) will be baked into the glyph outlines. Make - // NVPR just fill the baked shapes. fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, &fContextInitialMatrix, false); fGlyphs = get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(), - &fGlyphCache->getDescriptor(), - SkStrokeRec(SkStrokeRec::kFill_InitStyle)); + &fGlyphCache->getDescriptor(), fStroke); } else { // Don't bake strokes into the glyph outlines. We will stroke the glyphs // using the GPU instead. This is the fast path. - SkStrokeRec gpuStroke = SkStrokeRec(fSkPaint); + fStroke = SkStrokeRec(fSkPaint); fSkPaint.setStyle(SkPaint::kFill_Style); - if (gpuStroke.isHairlineStyle()) { + if (fStroke.isHairlineStyle()) { // Approximate hairline stroke. SkScalar strokeWidth = SK_Scalar1 / (SkVector::Make(fContextInitialMatrix.getScaleX(), fContextInitialMatrix.getSkewY()).length()); - gpuStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/); + fStroke.setStrokeStyle(strokeWidth, false /*strokeAndFill*/); } else if (fSkPaint.isFakeBoldText() && #ifdef SK_USE_FREETYPE_EMBOLDEN kMaxPerformance_RenderMode == renderMode && #endif - SkStrokeRec::kStroke_Style != gpuStroke.getStyle()) { + SkStrokeRec::kStroke_Style != fStroke.getStyle()) { // Instead of baking fake bold into the glyph outlines, do it with the GPU stroke. SkScalar fakeBoldScale = SkScalarInterpFunc(fSkPaint.getTextSize(), @@ -285,8 +288,8 @@ void GrStencilAndCoverTextContext::init(const GrPaint& paint, kStdFakeBoldInterpValues, kStdFakeBoldInterpLength); SkScalar extra = SkScalarMul(fSkPaint.getTextSize(), fakeBoldScale); - gpuStroke.setStrokeStyle(gpuStroke.needToApply() ? gpuStroke.getWidth() + extra : extra, - true /*strokeAndFill*/); + fStroke.setStrokeStyle(fStroke.needToApply() ? fStroke.getWidth() + extra : extra, + true /*strokeAndFill*/); fSkPaint.setFakeBoldText(false); } @@ -299,9 +302,9 @@ void GrStencilAndCoverTextContext::init(const GrPaint& paint, fTextInverseRatio = SkPaint::kCanonicalTextSizeForPaths / fSkPaint.getTextSize(); // Compensate for the glyphs being scaled by fTextRatio. - if (!gpuStroke.isFillStyle()) { - gpuStroke.setStrokeStyle(gpuStroke.getWidth() / fTextRatio, - SkStrokeRec::kStrokeAndFill_Style == gpuStroke.getStyle()); + if (!fStroke.isFillStyle()) { + fStroke.setStrokeStyle(fStroke.getWidth() / fTextRatio, + SkStrokeRec::kStrokeAndFill_Style == fStroke.getStyle()); } fSkPaint.setLinearText(true); @@ -328,9 +331,9 @@ void GrStencilAndCoverTextContext::init(const GrPaint& paint, fGlyphCache = fSkPaint.detachCache(&fDeviceProperties, NULL, false); fGlyphs = canUseRawPaths ? - get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, gpuStroke) : + get_gr_glyphs(fContext, fSkPaint.getTypeface(), NULL, fStroke) : get_gr_glyphs(fContext, fGlyphCache->getScalerContext()->getTypeface(), - &fGlyphCache->getDescriptor(), gpuStroke); + &fGlyphCache->getDescriptor(), fStroke); } fStateRestore.set(&fDrawState); @@ -347,31 +350,90 @@ void GrStencilAndCoverTextContext::init(const GrPaint& paint, *fDrawState.stencil() = kStencilPass; - SkASSERT(0 == fPendingGlyphCount); + SkASSERT(0 == fQueuedGlyphCount); + SkASSERT(kGlyphBufferSize == fFallbackGlyphsIdx); } -inline void GrStencilAndCoverTextContext::appendGlyph(uint16_t glyphID, float x, float y) { - if (fPendingGlyphCount >= kGlyphBufferSize) { +bool GrStencilAndCoverTextContext::mapToFallbackContext(GrContext::AutoMatrix& autoMatrix, + SkMatrix* inverse) { + // The current view matrix is flipped because GPU path rendering glyphs have an + // inverted y-direction. Unflip the view matrix for the fallback context. If using + // device-space glyphs, we'll also need to restore the original view matrix since + // we moved that transfomation into our local glyph cache for this scenario. Also + // track the inverse operation so the caller can unmap the paint and glyph positions. + if (fUsingDeviceSpaceGlyphs) { + autoMatrix.set(fContext, fContextInitialMatrix); + if (!fContextInitialMatrix.invert(inverse)) { + return false; + } + inverse->preScale(1, -1); + } else { + inverse->setScale(1, -1); + const SkMatrix& unflip = *inverse; // unflip is equal to its own inverse. + autoMatrix.setPreConcat(fContext, unflip); + } + return true; +} + +inline void GrStencilAndCoverTextContext::appendGlyph(const SkGlyph& glyph, const SkPoint& pos) { + if (fQueuedGlyphCount >= fFallbackGlyphsIdx) { + SkASSERT(fQueuedGlyphCount == fFallbackGlyphsIdx); this->flush(); } - fIndexBuffer[fPendingGlyphCount] = glyphID; - fTransformBuffer[2 * fPendingGlyphCount] = fTextInverseRatio * x; - fTransformBuffer[2 * fPendingGlyphCount + 1] = -fTextInverseRatio * y; + // Stick the glyphs we can't draw at the end of the buffer, growing backwards. + int index = (SkMask::kARGB32_Format == glyph.fMaskFormat) ? + --fFallbackGlyphsIdx : fQueuedGlyphCount++; - ++fPendingGlyphCount; + fGlyphIndices[index] = glyph.getGlyphID(); + fGlyphPositions[index].set(fTextInverseRatio * pos.x(), -fTextInverseRatio * pos.y()); +} + +static const SkScalar* get_xy_scalar_array(const SkPoint* pointArray) { + GR_STATIC_ASSERT(2 * sizeof(SkScalar) == sizeof(SkPoint)); + GR_STATIC_ASSERT(0 == offsetof(SkPoint, fX)); + + return &pointArray[0].fX; } void GrStencilAndCoverTextContext::flush() { - if (0 == fPendingGlyphCount) { - return; + if (fQueuedGlyphCount > 0) { + fDrawTarget->drawPaths(&fDrawState, fGlyphs, + fGlyphIndices, GrPathRange::kU16_PathIndexType, + get_xy_scalar_array(fGlyphPositions), + GrPathRendering::kTranslate_PathTransformType, + fQueuedGlyphCount, GrPathRendering::kWinding_FillType); + + fQueuedGlyphCount = 0; } - fDrawTarget->drawPaths(&fDrawState, fGlyphs, fIndexBuffer, GrPathRange::kU16_PathIndexType, - fTransformBuffer, GrPathRendering::kTranslate_PathTransformType, - fPendingGlyphCount, GrPathRendering::kWinding_FillType); + if (fFallbackGlyphsIdx < kGlyphBufferSize) { + int fallbackGlyphCount = kGlyphBufferSize - fFallbackGlyphsIdx; - fPendingGlyphCount = 0; + GrPaint paintFallback(fPaint); + + SkPaint skPaintFallback(fSkPaint); + if (!fUsingDeviceSpaceGlyphs) { + fStroke.applyToPaint(&skPaintFallback); + } + skPaintFallback.setTextAlign(SkPaint::kLeft_Align); // Align has already been accounted for. + skPaintFallback.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + + GrContext::AutoMatrix autoMatrix; + SkMatrix inverse; + if (this->mapToFallbackContext(autoMatrix, &inverse)) { + paintFallback.localCoordChangeInverse(inverse); + inverse.mapPoints(&fGlyphPositions[fFallbackGlyphsIdx], fallbackGlyphCount); + } + + fFallbackTextContext->drawPosText(paintFallback, skPaintFallback, + (char*)&fGlyphIndices[fFallbackGlyphsIdx], + 2 * fallbackGlyphCount, + get_xy_scalar_array(&fGlyphPositions[fFallbackGlyphsIdx]), + 2, SkPoint::Make(0, 0)); + + fFallbackGlyphsIdx = kGlyphBufferSize; + } } void GrStencilAndCoverTextContext::finish() { diff --git a/src/gpu/GrStencilAndCoverTextContext.h b/src/gpu/GrStencilAndCoverTextContext.h index 88b4eec71a..1f608860ed 100644 --- a/src/gpu/GrStencilAndCoverTextContext.h +++ b/src/gpu/GrStencilAndCoverTextContext.h @@ -57,11 +57,13 @@ private: float fTextInverseRatio; SkGlyphCache* fGlyphCache; GrPathRange* fGlyphs; - uint16_t fIndexBuffer[kGlyphBufferSize]; - float fTransformBuffer[2 * kGlyphBufferSize]; - int fPendingGlyphCount; + SkStrokeRec fStroke; + uint16_t fGlyphIndices[kGlyphBufferSize]; + SkPoint fGlyphPositions[kGlyphBufferSize]; + int fQueuedGlyphCount; + int fFallbackGlyphsIdx; SkMatrix fContextInitialMatrix; - bool fNeedsDeviceSpaceGlyphs; + bool fUsingDeviceSpaceGlyphs; GrStencilAndCoverTextContext(GrContext*, const SkDeviceProperties&); @@ -76,7 +78,8 @@ private: const SkPoint& offset) SK_OVERRIDE; void init(const GrPaint&, const SkPaint&, size_t textByteLength, RenderMode); - void appendGlyph(uint16_t glyphID, float x, float y); + bool mapToFallbackContext(GrContext::AutoMatrix&, SkMatrix* inverse); + void appendGlyph(const SkGlyph&, const SkPoint&); void flush(); void finish();