Chromium Code Reviews| Index: src/gpu/GrStencilAndCoverTextContext.cpp |
| diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..359b61e28ecbd9fea00f0ae4a81356d68f46356d |
| --- /dev/null |
| +++ b/src/gpu/GrStencilAndCoverTextContext.cpp |
| @@ -0,0 +1,399 @@ |
| +/* |
| + * 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 "SkTextMapState.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->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; |
| + |
| + // 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 halfSampleX, halfSampleY; |
| + if (cache->isSubpixel()) { |
| + halfSampleX = halfSampleY = (SK_FixedHalf >> SkGlyph::kSubBits); |
| + SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(fContext->getMatrix()); |
| + if (kX_SkAxisAlignment == baseline) { |
| + halfSampleY = SK_FixedHalf; |
| + } else if (kY_SkAxisAlignment == baseline) { |
| + halfSampleX = SK_FixedHalf; |
| + } |
| + } else { |
| + halfSampleX = halfSampleY = SK_FixedHalf; |
| + } |
| + |
| + SkFixed fx = SkScalarToFixed(x) + halfSampleX; |
|
jvanverth1
2014/06/09 13:48:07
Doesn't this lead to glyphs being off by half a pi
bungeman-skia
2014/06/09 15:00:07
Yeah, this was done in SkDraw because of SkDraw1Gl
Kimmo Kinnunen
2014/06/11 12:33:24
Done.
|
| + SkFixed fy = SkScalarToFixed(y) + halfSampleY; |
| + 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->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); |
| + |
| + SkMatrix ctm = fContext->getMatrix(); |
| + |
| + const char* stop = text + byteLength; |
| + SkTextAlignProcScalar alignProc(fSkPaint.getTextAlign()); |
| + SkTextMapState tms(SkMatrix::I(), constY); |
| + SkTextMapState::Proc tmsProc = tms.pickProc(scalarsPerPosition); |
| + |
| + SkTDArray<const GrPath*> paths; |
| + SkTDArray<SkMatrix> transforms; |
| + SkScalar halfSampleX = 0, halfSampleY = 0; |
| + |
| + if (cache->isSubpixel()) { |
| + // maybe we should skip the rounding if linearText is set |
| + SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(ctm); |
| + |
| +#ifndef SK_IGNORE_SUBPIXEL_AXIS_ALIGN_FIX |
| + if (kX_SkAxisAlignment == baseline) { |
| + halfSampleY = SK_ScalarHalf; |
| + } else if (kY_SkAxisAlignment == baseline) { |
| + halfSampleX = SK_ScalarHalf; |
| + } |
| +#endif |
| + |
| + if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
| + while (text < stop) { |
| + tmsProc(tms, pos); |
| + const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| + if (glyph.fWidth) { |
| + SkScalar x = tms.fLoc.fX + halfSampleX; |
|
jvanverth1
2014/06/09 13:48:07
Same issue with half-pixel alignment as above? (a
Kimmo Kinnunen
2014/06/11 12:33:24
Done.
|
| + SkScalar y = tms.fLoc.fY + halfSampleY; |
| + |
| + this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
| + glyph.getSubXFixed(), |
| + glyph.getSubYFixed()), |
| + SkPoint::Make(x, y), |
| + strike, |
| + scaler); |
| + } |
| + pos += scalarsPerPosition; |
| + } |
| + } else { |
| + while (text < stop) { |
| + const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| + |
| + if (glyph.fWidth) { |
| + tmsProc(tms, pos); |
| + SkPoint loc; |
| + alignProc(tms.fLoc, glyph, &loc); |
| + |
| + loc.fX += halfSampleX; |
| + loc.fY += halfSampleY; |
| + |
| + this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
| + glyph.getSubXFixed(), |
| + glyph.getSubYFixed()), |
| + loc, |
| + strike, |
| + scaler); |
| + |
| + } |
| + pos += scalarsPerPosition; |
| + } |
| + } |
| + } else { // Codepath for "not subpixel" case. |
| + if (SkPaint::kLeft_Align == fSkPaint.getTextAlign()) { |
|
jvanverth1
2014/06/09 13:48:07
Do we need to support non-subpixel layout when dra
Kimmo Kinnunen
2014/06/11 12:33:24
Done.
Kimmo Kinnunen
2014/06/11 12:39:21
Actually, I mean to say that I couldn't get the gl
|
| + while (text < stop) { |
| + const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| + |
| + if (glyph.fWidth) { |
| + tmsProc(tms, pos); |
| + |
| + SkScalar x = tms.fLoc.fX + SK_ScalarHalf; //halfSampleX; |
| + SkScalar y = tms.fLoc.fY + SK_ScalarHalf; //halfSampleY; |
| + this->appendGlyph(GrGlyph::Pack(glyph.getGlyphID(), |
| + glyph.getSubXFixed(), |
| + glyph.getSubYFixed()), |
| + SkPoint::Make(x, y), |
| + strike, |
| + scaler); |
| + } |
| + pos += scalarsPerPosition; |
| + } |
| + } else { |
| + while (text < stop) { |
| + const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0); |
| + |
| + if (glyph.fWidth) { |
| + tmsProc(tms, pos); |
| + |
| + SkPoint loc; |
| + alignProc(tms.fLoc, glyph, &loc); |
| + |
| + loc.fX += SK_ScalarHalf; //halfSampleX; |
| + loc.fY += SK_ScalarHalf; //halfSampleY; |
| + 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::kFill_Style |
| + && paint.getStrokeWidth() == 0 |
| + && (fContext->getMatrix().hasPerspective() |
| + || !fContext->getMatrix().invert(NULL))) { |
| + 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); |
| + |
| + if (SkDraw::ShouldDrawTextAsPaths(skPaint, fContext->getMatrix())) { |
| + // This is to reproduce SkDraw::drawText_asPaths glyph positions. |
| + fSkPaint.setLinearText(true); |
| + fTextRatio = fSkPaint.getTextSize() / SkPaint::kCanonicalTextSizeForPaths; |
| + fSkPaint.setTextSize(SkIntToScalar(SkPaint::kCanonicalTextSizeForPaths)); |
| + } else { |
| + fTextRatio = 1.0f; |
| + } |
| + // Note: if Chrome ever draws text in very different text sizes in real |
| + // time, then glyphs need to be looked up untransformed and scaled, but |
| + // still based on the requested text size. This will let the path cache |
| + // work. This will probably not be needed until 1) text size is animating, |
| + // 2) layout change animations are fast enough 3) pictures are rasterized |
| + // directly to framebuffer. |
| + |
| + if (fSkPaint.getStyle() != SkPaint::kFill_Style) { |
| + SkScalar strokeWidth = fSkPaint.getStrokeWidth(); |
| + if (0 == strokeWidth) { |
| + SkMatrix inv; |
| + if (fContext->getMatrix().invert(&inv)) { |
|
jvanverth1
2014/06/09 13:48:07
This won't work for non-uniform scale. And if you'
Kimmo Kinnunen
2014/06/11 12:33:24
Right..
|
| + strokeWidth = SK_Scalar1 * inv.getScaleX(); |
| + } else { |
| + // Avoid unused return value warning. |
| + SkASSERT(false); |
| + strokeWidth = SK_Scalar1; |
| + } |
| + } |
| + // Compensate the glyphs being scaled up by fTextRatio by scaling the |
| + // stroke down. |
| + fSkPaint.setStrokeWidth(strokeWidth / fTextRatio); |
| + } |
| + fStroke = SkStrokeRec(fSkPaint); |
| + |
| + // Make glyph cache produce paths geometry for fill. We will stroke them |
| + // by passing fStroke to drawPath. |
| + 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); |
| + GrTextContext::finish(); |
| +} |
| + |