| 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();
|
| +}
|
| +
|
|
|