add size limit for using glyphcache. above that, draw using paths
authorreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 4 Jun 2013 16:56:27 +0000 (16:56 +0000)
committerreed@google.com <reed@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 4 Jun 2013 16:56:27 +0000 (16:56 +0000)
BUG=
R=bungeman@google.com

Review URL: https://codereview.chromium.org/16336024

git-svn-id: http://skia.googlecode.com/svn/trunk@9429 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkDraw.h
include/core/SkPaint.h
src/core/SkDraw.cpp
src/core/SkPaint.cpp
src/core/SkScalerContext.cpp

index 2a8afc4..293d4e3 100644 (file)
@@ -103,6 +103,10 @@ private:
     void    drawDevMask(const SkMask& mask, const SkPaint&) const;
     void    drawBitmapAsMask(const SkBitmap&, const SkPaint&) const;
 
+    void    drawPosText_asPaths(const char text[], size_t byteLength,
+                                const SkScalar pos[], SkScalar constY,
+                                int scalarsPerPosition, const SkPaint&) const;
+    
     /**
      *  Return the current clip bounds, in local coordinates, with slop to account
      *  for antialiasing or hairlines (i.e. device-bounds outset by 1, and then
@@ -114,6 +118,8 @@ private:
     bool SK_WARN_UNUSED_RESULT
     computeConservativeLocalClipBounds(SkRect* bounds) const;
 
+    static bool ShouldDrawTextAsPaths(const SkPaint&, const SkMatrix&);
+
 public:
     const SkBitmap* fBitmap;        // required
     const SkMatrix* fMatrix;        // required
index a6c9bd4..624f39c 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "SkColor.h"
 #include "SkDrawLooper.h"
+#include "SkMatrix.h"
 #include "SkXfermode.h"
 #ifdef SK_BUILD_FOR_ANDROID
 #include "SkPaintOptionsAndroid.h"
@@ -30,7 +31,6 @@ struct SkRect;
 class SkGlyphCache;
 class SkImageFilter;
 class SkMaskFilter;
-class SkMatrix;
 class SkPath;
 class SkPathEffect;
 struct SkPoint;
@@ -935,6 +935,22 @@ public:
     const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage,
                                       Style) const;
 
+    /**
+     *  Return a matrix that applies the paint's text values: size, scale, skew
+     */
+    static SkMatrix* SetTextMatrix(SkMatrix* matrix, SkScalar size,
+                                   SkScalar scaleX, SkScalar skewX) {
+        matrix->setScale(size * scaleX, size);
+        if (skewX) {
+            matrix->postSkew(skewX, 0);
+        }
+        return matrix;
+    }
+    
+    SkMatrix* setTextMatrix(SkMatrix* matrix) const {
+        return SetTextMatrix(matrix, fTextSize, fTextScaleX, fTextSkewX);
+    }
+    
     SkDEVCODE(void toString(SkString*) const;)
 
 private:
@@ -989,14 +1005,53 @@ private:
     static void Term();
 
     enum {
-        kCanonicalTextSizeForPaths = 64
+        /*  This is the size we use when we ask for a glyph's path. We then
+         *  post-transform it as we draw to match the request.
+         *  This is done to try to re-use cache entries for the path.
+         *
+         *  This value is somewhat arbitrary. In theory, it could be 1, since
+         *  we store paths as floats. However, we get the path from the font
+         *  scaler, and it may represent its paths as fixed-point (or 26.6),
+         *  so we shouldn't ask for something too big (might overflow 16.16)
+         *  or too small (underflow 26.6).
+         *
+         *  This value could track kMaxSizeForGlyphCache, assuming the above
+         *  constraints, but since we ask for unhinted paths, the two values
+         *  need not match per-se.
+         */
+        kCanonicalTextSizeForPaths  = 64,
+
+        /*
+         *  Above this size (taking into account CTM and textSize), we never use
+         *  the cache for bits or metrics (we might overflow), so we just ask
+         *  for a caononical size and post-transform that.
+         */
+        kMaxSizeForGlyphCache       = 256,
     };
+
+    static bool TooBigToUseCache(const SkMatrix& ctm, const SkMatrix& textM);
+
+    bool tooBigToUseCache() const;
+    bool tooBigToUseCache(const SkMatrix& ctm) const;
+    
+    // Set flags/hinting/textSize up to use for drawing text as paths.
+    // Returns scale factor to restore the original textSize, since will will
+    // have change it to kCanonicalTextSizeForPaths.
+    SkScalar setupForAsPaths();
+
+    static SkScalar MaxCacheSize2() {
+        static const SkScalar kMaxSize = SkIntToScalar(kMaxSizeForGlyphCache);
+        static const SkScalar kMag2Max = kMaxSize * kMaxSize;
+        return kMag2Max;
+    }
+
     friend class SkAutoGlyphCache;
     friend class SkCanvas;
     friend class SkDraw;
     friend class SkGraphics; // So Term() can be called.
     friend class SkPDFDevice;
     friend class SkTextToPathIter;
+    friend class SkCanonicalizePaint;
 
 #ifdef SK_BUILD_FOR_ANDROID
     SkPaintOptionsAndroid fPaintOptionsAndroid;
index fc615ab..f313c4b 100644 (file)
 #include "SkDrawProcs.h"
 #include "SkMatrixUtils.h"
 
+bool SkDraw::ShouldDrawTextAsPaths(const SkPaint& paint, const SkMatrix& ctm) {
+    // we don't cache hairlines in the cache
+    if (SkPaint::kStroke_Style == paint.getStyle() &&
+            0 == paint.getStrokeWidth()) {
+        return true;
+    }
+    
+    // we don't cache perspective
+    if (ctm.hasPerspective()) {
+        return true;
+    }
+
+    SkMatrix textM;
+    return SkPaint::TooBigToUseCache(ctm, *paint.setTextMatrix(&textM));
+}
+
 //#define TRACE_BITMAP_DRAWS
 
 #define kBlitterStorageLongCount    (sizeof(SkBitmapProcShader) >> 2)
@@ -1663,9 +1679,7 @@ void SkDraw::drawText(const char text[], size_t byteLength,
 
     // SkScalarRec doesn't currently have a way of representing hairline stroke and
     // will fill if its frame-width is 0.
-    if (/*paint.isLinearText() ||*/
-        (fMatrix->hasPerspective()) ||
-        (0 == paint.getStrokeWidth() && SkPaint::kStroke_Style == paint.getStyle())) {
+    if (ShouldDrawTextAsPaths(paint, *fMatrix)) {
         this->drawText_asPaths(text, byteLength, x, y, paint);
         return;
     }
@@ -1839,6 +1853,50 @@ TextMapState::Proc TextMapState::pickProc(int scalarsPerPosition) {
 
 //////////////////////////////////////////////////////////////////////////////
 
+void SkDraw::drawPosText_asPaths(const char text[], size_t byteLength,
+                                 const SkScalar pos[], SkScalar constY,
+                                 int scalarsPerPosition,
+                                 const SkPaint& origPaint) const {
+    // setup our std paint, in hopes of getting hits in the cache
+    SkPaint paint(origPaint);
+    SkScalar matrixScale = paint.setupForAsPaths();
+
+    SkDraw draw(*this);
+
+    // Now modify our matrix to account for the canonical text size
+    SkMatrix matrix = *fMatrix;
+    matrix.preScale(matrixScale, matrixScale);
+    const SkScalar posScale = SkScalarInvert(matrixScale);
+    
+    SkDrawCacheProc     glyphCacheProc = paint.getDrawCacheProc();
+    SkAutoGlyphCache    autoCache(paint, NULL, NULL);
+    SkGlyphCache*       cache = autoCache.getCache();
+
+    const char*        stop = text + byteLength;
+    AlignProc          alignProc = pick_align_proc(paint.getTextAlign());
+    TextMapState       tms(SkMatrix::I(), constY);
+    TextMapState::Proc tmsProc = tms.pickProc(scalarsPerPosition);
+    
+    while (text < stop) {
+        const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
+        if (glyph.fWidth) {
+            const SkPath* path = cache->findPath(glyph);
+            if (path) {
+                tmsProc(tms, pos);
+                SkIPoint fixedLoc;
+                alignProc(tms.fLoc, glyph, &fixedLoc);
+                
+                SkMatrix localMatrix = matrix;
+                localMatrix.preTranslate(SkFixedToScalar(fixedLoc.fX) * posScale,
+                                         SkFixedToScalar(fixedLoc.fY) * posScale);
+                draw.fMatrix = &localMatrix;
+                draw.drawPath(*path, paint);
+            }
+        }
+        pos += scalarsPerPosition;
+    }
+}
+
 void SkDraw::drawPosText(const char text[], size_t byteLength,
                          const SkScalar pos[], SkScalar constY,
                          int scalarsPerPosition, const SkPaint& paint) const {
@@ -1852,10 +1910,9 @@ void SkDraw::drawPosText(const char text[], size_t byteLength,
         return;
     }
 
-    if (/*paint.isLinearText() ||*/
-        (fMatrix->hasPerspective())) {
-        // TODO !!!!
-//      this->drawText_asPaths(text, byteLength, x, y, paint);
+    if (ShouldDrawTextAsPaths(paint, *fMatrix)) {
+        this->drawPosText_asPaths(text, byteLength, pos, constY,
+                                  scalarsPerPosition, paint);
         return;
     }
 
index 9b6b883..e781cd4 100644 (file)
@@ -30,6 +30,7 @@
 #include "SkStroke.h"
 #include "SkTextFormatParams.h"
 #include "SkTextToPathIter.h"
+#include "SkTLazy.h"
 #include "SkTypeface.h"
 #include "SkXfermode.h"
 
@@ -425,6 +426,37 @@ SkAnnotation* SkPaint::setAnnotation(SkAnnotation* annotation) {
 
 ///////////////////////////////////////////////////////////////////////////////
 
+static SkScalar mag2(SkScalar x, SkScalar y) {
+    return x * x + y * y;
+}
+
+static bool tooBig(const SkMatrix& m, SkScalar ma2max) {
+    return  mag2(m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewY]) > ma2max
+            ||
+            mag2(m[SkMatrix::kMSkewX], m[SkMatrix::kMScaleY]) > ma2max;
+}
+
+bool SkPaint::TooBigToUseCache(const SkMatrix& ctm, const SkMatrix& textM) {
+    SkASSERT(!ctm.hasPerspective());
+    SkASSERT(!textM.hasPerspective());
+
+    SkMatrix matrix;
+    matrix.setConcat(ctm, textM);
+    return tooBig(matrix, MaxCacheSize2());
+}
+
+bool SkPaint::tooBigToUseCache(const SkMatrix& ctm) const {
+    SkMatrix textM;
+    return TooBigToUseCache(ctm, *this->setTextMatrix(&textM));
+}
+
+bool SkPaint::tooBigToUseCache() const {
+    SkMatrix textM;
+    return tooBig(*this->setTextMatrix(&textM), MaxCacheSize2());
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
 #include "SkGlyphCache.h"
 #include "SkUtils.h"
 
@@ -915,33 +947,51 @@ SkDrawCacheProc SkPaint::getDrawCacheProc() const {
 
 ///////////////////////////////////////////////////////////////////////////////
 
-class SkAutoRestorePaintTextSizeAndFrame {
+#define TEXT_AS_PATHS_PAINT_FLAGS_TO_IGNORE (   \
+SkPaint::kDevKernText_Flag          |       \
+SkPaint::kLinearText_Flag           |       \
+SkPaint::kLCDRenderText_Flag        |       \
+SkPaint::kEmbeddedBitmapText_Flag   |       \
+SkPaint::kAutoHinting_Flag          |       \
+SkPaint::kGenA8FromLCD_Flag )
+
+SkScalar SkPaint::setupForAsPaths() {
+    uint32_t flags = this->getFlags();
+    // clear the flags we don't care about
+    flags &= ~TEXT_AS_PATHS_PAINT_FLAGS_TO_IGNORE;
+    // set the flags we do care about
+    flags |= SkPaint::kSubpixelText_Flag;
+    
+    this->setFlags(flags);
+    this->setHinting(SkPaint::kNo_Hinting);
+    
+    SkScalar textSize = fTextSize;
+    this->setTextSize(kCanonicalTextSizeForPaths);
+    return textSize / kCanonicalTextSizeForPaths;
+}
+
+class SkCanonicalizePaint {
 public:
-    SkAutoRestorePaintTextSizeAndFrame(const SkPaint* paint)
-            : fPaint((SkPaint*)paint) {
-#ifdef SK_BUILD_FOR_ANDROID
-        fGenerationID = fPaint->getGenerationID();
-#endif
-        fTextSize = paint->getTextSize();
-        fStyle = paint->getStyle();
-        fPaint->setStyle(SkPaint::kFill_Style);
+    SkCanonicalizePaint(const SkPaint& paint) : fPaint(&paint), fScale(0) {
+        if (paint.isLinearText() || paint.tooBigToUseCache()) {
+            SkPaint* p = fLazy.set(paint);
+            fScale = p->setupForAsPaths();
+            fPaint = p;
+        }
     }
 
-    ~SkAutoRestorePaintTextSizeAndFrame() {
-        fPaint->setStyle(fStyle);
-        fPaint->setTextSize(fTextSize);
-#ifdef SK_BUILD_FOR_ANDROID
-        fPaint->setGenerationID(fGenerationID);
-#endif
-    }
+    const SkPaint& getPaint() const { return *fPaint; }
+    
+    /**
+     *  Returns 0 if the paint was unmodified, or the scale factor need to
+     *  the original textSize
+     */
+    SkScalar getScale() const { return fScale; }
 
 private:
-    SkPaint*        fPaint;
-    SkScalar        fTextSize;
-    SkPaint::Style  fStyle;
-#ifdef SK_BUILD_FOR_ANDROID
-    uint32_t        fGenerationID;
-#endif
+    const SkPaint*   fPaint;
+    SkScalar         fScale;
+    SkTLazy<SkPaint> fLazy;
 };
 
 static void set_bounds(const SkGlyph& g, SkRect* bounds) {
@@ -1069,14 +1119,9 @@ SkScalar SkPaint::measureText(const void* textData, size_t length,
     const char* text = (const char*)textData;
     SkASSERT(text != NULL || length == 0);
 
-    SkScalar                            scale = 0;
-    SkAutoRestorePaintTextSizeAndFrame  restore(this);
-
-    if (this->isLinearText()) {
-        scale = fTextSize / kCanonicalTextSizeForPaths;
-        // this gets restored by restore
-        ((SkPaint*)this)->setTextSize(SkIntToScalar(kCanonicalTextSizeForPaths));
-    }
+    SkCanonicalizePaint canon(*this);
+    const SkPaint& paint = canon.getPaint();
+    SkScalar scale = canon.getScale();
 
     SkMatrix zoomMatrix, *zoomPtr = NULL;
     if (zoom) {
@@ -1084,7 +1129,7 @@ SkScalar SkPaint::measureText(const void* textData, size_t length,
         zoomPtr = &zoomMatrix;
     }
 
-    SkAutoGlyphCache    autoCache(*this, NULL, zoomPtr);
+    SkAutoGlyphCache    autoCache(paint, NULL, zoomPtr);
     SkGlyphCache*       cache = autoCache.getCache();
 
     SkScalar width = 0;
@@ -1092,7 +1137,7 @@ SkScalar SkPaint::measureText(const void* textData, size_t length,
     if (length > 0) {
         int tempCount;
 
-        width = this->measure_text(cache, text, length, &tempCount, bounds);
+        width = paint.measure_text(cache, text, length, &tempCount, bounds);
         if (scale) {
             width = SkScalarMul(width, scale);
             if (bounds) {
@@ -1153,23 +1198,22 @@ size_t SkPaint::breakText(const void* textD, size_t length, SkScalar maxWidth,
     SkASSERT(textD != NULL);
     const char* text = (const char*)textD;
 
-    SkScalar                            scale = 0;
-    SkAutoRestorePaintTextSizeAndFrame  restore(this);
+    SkCanonicalizePaint canon(*this);
+    const SkPaint& paint = canon.getPaint();
+    SkScalar scale = canon.getScale();
 
-    if (this->isLinearText()) {
-        scale = fTextSize / kCanonicalTextSizeForPaths;
-        maxWidth = SkScalarMulDiv(maxWidth, kCanonicalTextSizeForPaths, fTextSize);
-        // this gets restored by restore
-        ((SkPaint*)this)->setTextSize(SkIntToScalar(kCanonicalTextSizeForPaths));
+    // adjust max in case we changed the textSize in paint
+    if (scale) {
+        maxWidth /= scale;
     }
 
-    SkAutoGlyphCache    autoCache(*this, NULL, NULL);
+    SkAutoGlyphCache    autoCache(paint, NULL, NULL);
     SkGlyphCache*       cache = autoCache.getCache();
 
-    SkMeasureCacheProc glyphCacheProc = this->getMeasureCacheProc(tbd, false);
+    SkMeasureCacheProc glyphCacheProc = paint.getMeasureCacheProc(tbd, false);
     const char*      stop;
     SkTextBufferPred pred = chooseTextBufferPred(tbd, &text, length, &stop);
-    const int        xyIndex = this->isVerticalText() ? 1 : 0;
+    const int        xyIndex = paint.isVerticalText() ? 1 : 0;
     // use 64bits for our accumulator, to avoid overflowing 16.16
     Sk48Dot16        max = SkScalarToFixed(maxWidth);
     Sk48Dot16        width = 0;
@@ -1227,15 +1271,10 @@ static void FontMetricsDescProc(SkTypeface* typeface, const SkDescriptor* desc,
 }
 
 SkScalar SkPaint::getFontMetrics(FontMetrics* metrics, SkScalar zoom) const {
-    SkScalar                            scale = 0;
-    SkAutoRestorePaintTextSizeAndFrame  restore(this);
-
-    if (this->isLinearText()) {
-        scale = fTextSize / kCanonicalTextSizeForPaths;
-        // this gets restored by restore
-        ((SkPaint*)this)->setTextSize(SkIntToScalar(kCanonicalTextSizeForPaths));
-    }
-
+    SkCanonicalizePaint canon(*this);
+    const SkPaint& paint = canon.getPaint();
+    SkScalar scale = canon.getScale();
+    
     SkMatrix zoomMatrix, *zoomPtr = NULL;
     if (zoom) {
         zoomMatrix.setScale(zoom, zoom);
@@ -1247,7 +1286,7 @@ SkScalar SkPaint::getFontMetrics(FontMetrics* metrics, SkScalar zoom) const {
         metrics = &storage;
     }
 
-    this->descriptorProc(NULL, zoomPtr, FontMetricsDescProc, metrics, true);
+    paint.descriptorProc(NULL, zoomPtr, FontMetricsDescProc, metrics, true);
 
     if (scale) {
         metrics->fTop = SkScalarMul(metrics->fTop, scale);
@@ -1284,25 +1323,20 @@ int SkPaint::getTextWidths(const void* textData, size_t byteLength,
         return this->countText(textData, byteLength);
     }
 
-    SkAutoRestorePaintTextSizeAndFrame  restore(this);
-    SkScalar                            scale = 0;
-
-    if (this->isLinearText()) {
-        scale = fTextSize / kCanonicalTextSizeForPaths;
-        // this gets restored by restore
-        ((SkPaint*)this)->setTextSize(SkIntToScalar(kCanonicalTextSizeForPaths));
-    }
+    SkCanonicalizePaint canon(*this);
+    const SkPaint& paint = canon.getPaint();
+    SkScalar scale = canon.getScale();
 
-    SkAutoGlyphCache    autoCache(*this, NULL, NULL);
+    SkAutoGlyphCache    autoCache(paint, NULL, NULL);
     SkGlyphCache*       cache = autoCache.getCache();
     SkMeasureCacheProc  glyphCacheProc;
-    glyphCacheProc = this->getMeasureCacheProc(kForward_TextBufferDirection,
+    glyphCacheProc = paint.getMeasureCacheProc(kForward_TextBufferDirection,
                                                NULL != bounds);
 
     const char* text = (const char*)textData;
     const char* stop = text + byteLength;
     int         count = 0;
-    const int   xyIndex = this->isVerticalText() ? 1 : 0;
+    const int   xyIndex = paint.isVerticalText() ? 1 : 0;
 
     if (this->isDevKernText()) {
         // we adjust the widths returned here through auto-kerning
index 1f71bf0..521cb9b 100644 (file)
@@ -791,10 +791,7 @@ void SkScalerContextRec::getMatrixFrom2x2(SkMatrix* dst) const {
 }
 
 void SkScalerContextRec::getLocalMatrix(SkMatrix* m) const {
-    m->setScale(SkScalarMul(fTextSize, fPreScaleX), fTextSize);
-    if (fPreSkewX) {
-        m->postSkew(fPreSkewX, 0);
-    }
+    SkPaint::SetTextMatrix(m, fTextSize, fPreScaleX, fPreSkewX);
 }
 
 void SkScalerContextRec::getSingleMatrix(SkMatrix* m) const {