Update PDF backend to support fallback fonts on Android.
authordjsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 3 Oct 2013 14:42:24 +0000 (14:42 +0000)
committerdjsollen@google.com <djsollen@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 3 Oct 2013 14:42:24 +0000 (14:42 +0000)
R=edisonn@google.com, reed@google.com, vandebo@chromium.org

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

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

gm/androidfallback.cpp [new file with mode: 0644]
gyp/gmslides.gypi
include/ports/SkTypeface_android.h
src/pdf/SkPDFDevice.cpp
src/ports/SkFontConfigInterface_android.cpp

diff --git a/gm/androidfallback.cpp b/gm/androidfallback.cpp
new file mode 100644 (file)
index 0000000..3ef8f1c
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+
+namespace skiagm {
+
+class AndroidFallbackGM : public GM {
+public:
+    AndroidFallbackGM() {
+        this->setBGColor(0xFFCCCCCC);
+    }
+
+protected:
+    virtual SkString onShortName() SK_OVERRIDE {
+        return SkString("android_paint");
+    }
+
+    virtual SkISize onISize() SK_OVERRIDE {
+        return make_isize(500, 500);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
+
+        SkPaint paint;
+        paint.setTextEncoding(SkPaint::kUTF16_TextEncoding);
+        paint.setTextSize(24);
+
+#if SK_BUILD_FOR_ANDROID
+        SkPaintOptionsAndroid options = paint.getPaintOptionsAndroid();
+        options.setUseFontFallbacks(true);
+        paint.setPaintOptionsAndroid(options);
+#endif
+
+        // "ื foo ๅ…่ˆŒ bar เค•"
+        const uint16_t unicodeStr[] = {0x05D0, 0x0020, 0x0066, 0x006F, 0x006F, 0x0020, 0x514D,
+                                       0x820c, 0x0020, 0x0062, 0x0061, 0x0072, 0x0020, 0x0915};
+        const int strLength = sizeof(unicodeStr) / sizeof(uint16_t);
+        const int strByteLength = sizeof(unicodeStr);
+
+        SkScalar posX[strLength];
+        SkPoint posXY[strLength];
+
+        for (int i = 0; i < strLength; ++i) {
+            posX[i] = SkIntToScalar(i * 24);
+            posXY[i].fX = posX[i];
+            posXY[i].fY = SkIntToScalar(24 + i);
+        }
+
+        canvas->translate(SkIntToScalar(10), SkIntToScalar(25));
+        // This currently causes the PDF backend to assert
+        // canvas->drawText(unicodeStr, strByteLength, 0, 0, paint);
+
+        canvas->translate(0, SkIntToScalar(75));
+        canvas->drawPosTextH(unicodeStr, strByteLength, posX, 0, paint);
+
+#if SK_BUILD_FOR_ANDROID
+        options.setLanguage("ja");
+        paint.setPaintOptionsAndroid(options);
+#endif
+
+        canvas->translate(0, SkIntToScalar(75));
+        canvas->drawPosText(unicodeStr, strByteLength, posXY, paint);
+
+        SkPath path;
+        path.moveTo(0, 0);
+        path.quadTo(50.0f, 100.0f, 250.0f, 150.0f);
+
+        canvas->translate(0, SkIntToScalar(75));
+        canvas->drawTextOnPath(unicodeStr, strByteLength, path, NULL, paint);
+    }
+
+private:
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+#if SK_BUILD_FOR_ANDROID
+DEF_GM( return SkNEW(AndroidFallbackGM); )
+#endif
+
+}
index bace62f..5e2dfc1 100644 (file)
@@ -4,6 +4,7 @@
     '../gm/aaclip.cpp',
     '../gm/aarectmodes.cpp',
     '../gm/alphagradients.cpp',
+    '../gm/androidfallback.cpp',
     '../gm/arcofzorro.cpp',
     '../gm/arithmode.cpp',
     '../gm/beziereffects.cpp',
index 1ee923c..2166e08 100644 (file)
@@ -68,6 +68,22 @@ SK_API void SkUseTestFontConfigFile(const char* mainconf, const char* fallbackco
 SkTypeface* SkAndroidNextLogicalTypeface(SkFontID currFontID, SkFontID origFontID,
                                          const SkPaintOptionsAndroid& options);
 
+/**
+ * Given a glyphID (built using fallback font chaining) and its origin typeface
+ * return the actual typeface within the fallback chain that this glyphID
+ * resolves to. If no suitable typeface is found then NULL is returned. However,
+ * if returned typeface is not NULL it is assumed to be globally cached so the
+ * caller need not ref it.
+ *
+ * Optionally, if lower/upper bound params are provided and the returned
+ * typeface is not NULL, then these params are populated with the range of
+ * glyphIDs that this typeface is capable of resolving. The lower bound is
+ * inclusive while the upper bound is exclusive.
+ */
+SkTypeface* SkGetTypefaceForGlyphID(uint16_t glyphID, const SkTypeface* origTypeface,
+                                    const SkPaintOptionsAndroid& options,
+                                    int* lowerBounds = NULL, int* upperBounds = NULL);
+
 #endif // #ifdef SK_BUILD_FOR_ANDROID
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
 
index d13d30c..e4b07f0 100644 (file)
 #include "SkTypefacePriv.h"
 #include "SkTSet.h"
 
+#ifdef SK_BUILD_FOR_ANDROID
+#include "SkTypeface_android.h"
+
+struct TypefaceFallbackData {
+    SkTypeface* typeface;
+    int lowerBounds;
+    int upperBounds;
+
+    bool operator==(const TypefaceFallbackData& b) const {
+        return typeface == b.typeface &&
+               lowerBounds == b.lowerBounds &&
+               upperBounds == b.upperBounds;
+    }
+};
+#endif
+
 // Utility functions
 
 static void emit_pdf_color(SkColor color, SkWStream* result) {
@@ -1115,6 +1131,112 @@ void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
         return;
     }
 
+#ifdef SK_BUILD_FOR_ANDROID
+    /*
+     * In the case that we have enabled fallback fonts on Android we need to
+     * take the following steps to ensure that the PDF draws all characters,
+     * regardless of their underlying font file, correctly.
+     *
+     * 1. Convert input into GlyphID encoding if it currently is not
+     * 2. Iterate over the glyphIDs and identify the actual typeface that each
+     *    glyph resolves to
+     * 3. Iterate over those typefaces and recursively call this function with
+     *    only the glyphs (and their positions) that the typeface is capable of
+     *    resolving.
+     */
+    if (paint.getPaintOptionsAndroid().isUsingFontFallbacks()) {
+        uint16_t* glyphIDs = NULL;
+        SkGlyphStorage tmpStorage(0);
+        size_t numGlyphs = 0;
+
+        // convert to glyphIDs
+        if (paint.getTextEncoding() == SkPaint::kGlyphID_TextEncoding) {
+            numGlyphs = len / 2;
+            glyphIDs = reinterpret_cast<uint16_t*>(const_cast<void*>(text));
+        } else {
+            numGlyphs = paint.textToGlyphs(text, len, NULL);
+            tmpStorage.reset(numGlyphs);
+            paint.textToGlyphs(text, len, tmpStorage.get());
+            glyphIDs = tmpStorage.get();
+        }
+
+        // if no typeface is provided in the paint get the default
+        SkAutoTUnref<SkTypeface> origFace(SkSafeRef(paint.getTypeface()));
+        if (NULL == origFace.get()) {
+            origFace.reset(SkTypeface::RefDefault());
+        }
+        const uint16_t origGlyphCount = origFace->countGlyphs();
+
+        // keep a list of the already visited typefaces and some data about them
+        SkTDArray<TypefaceFallbackData> visitedTypefaces;
+
+        // find all the typefaces needed to resolve this run of text
+        bool usesOriginalTypeface = false;
+        for (uint16_t x = 0; x < numGlyphs; ++x) {
+            // optimization that checks to see if original typeface can resolve the glyph
+            if (glyphIDs[x] < origGlyphCount) {
+                usesOriginalTypeface = true;
+                continue;
+            }
+
+            // find the fallback typeface that supports this glyph
+            TypefaceFallbackData data;
+            data.typeface = SkGetTypefaceForGlyphID(glyphIDs[x], origFace.get(),
+                                                    paint.getPaintOptionsAndroid(),
+                                                    &data.lowerBounds, &data.upperBounds);
+            // add the typeface and its data if we don't have it
+            if (data.typeface && !visitedTypefaces.contains(data)) {
+                visitedTypefaces.push(data);
+            }
+        }
+
+        // if the original font was used then add it to the list as well
+        if (usesOriginalTypeface) {
+            TypefaceFallbackData* data = visitedTypefaces.push();
+            data->typeface = origFace.get();
+            data->lowerBounds = 0;
+            data->upperBounds = origGlyphCount;
+        }
+
+        // keep a scratch glyph and pos storage
+        SkAutoTMalloc<SkScalar> posStorage(len * scalarsPerPos);
+        SkScalar* tmpPos = posStorage.get();
+        SkGlyphStorage glyphStorage(numGlyphs);
+        uint16_t* tmpGlyphIDs = glyphStorage.get();
+
+        // loop through all the valid typefaces, trim the glyphs to only those
+        // resolved by the typeface, and then draw that run of glyphs
+        for (int x = 0; x < visitedTypefaces.count(); ++x) {
+            const TypefaceFallbackData& data = visitedTypefaces[x];
+
+            int tmpGlyphCount = 0;
+            for (uint16_t y = 0; y < numGlyphs; ++y) {
+                if (glyphIDs[y] >= data.lowerBounds && glyphIDs[y] < data.upperBounds) {
+                    tmpGlyphIDs[tmpGlyphCount] = glyphIDs[y] - data.lowerBounds;
+                    memcpy(&(tmpPos[tmpGlyphCount * scalarsPerPos]),
+                           &(pos[y * scalarsPerPos]),
+                           scalarsPerPos * sizeof(SkScalar));
+                    tmpGlyphCount++;
+                }
+            }
+
+            // recursively call this function with the right typeface
+            SkPaint tmpPaint = paint;
+            tmpPaint.setTypeface(data.typeface);
+            tmpPaint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+            // turn off fallback chaining
+            SkPaintOptionsAndroid paintOpts = tmpPaint.getPaintOptionsAndroid();
+            paintOpts.setUseFontFallbacks(false);
+            tmpPaint.setPaintOptionsAndroid(paintOpts);
+
+            this->drawPosText(d, tmpGlyphIDs, tmpGlyphCount * 2, tmpPos, constY,
+                              scalarsPerPos, tmpPaint);
+        }
+        return;
+    }
+#endif
+
     SkGlyphStorage storage(0);
     uint16_t* glyphIDs = NULL;
     size_t numGlyphs = force_glyph_encoding(paint, text, len, &storage,
index 7382d00..9c188d9 100644 (file)
@@ -104,6 +104,9 @@ public:
                                    SkPaintOptionsAndroid::FontVariant fontVariant);
     SkTypeface* nextLogicalTypeface(SkFontID currFontID, SkFontID origFontID,
                                     const SkPaintOptionsAndroid& options);
+    SkTypeface* getTypefaceForGlyphID(uint16_t glyphID, const SkTypeface* origTypeface,
+                                      const SkPaintOptionsAndroid& options,
+                                      int* lowerBounds, int* upperBounds);
 
 private:
     void addFallbackFamily(FamilyRecID fontRecID);
@@ -672,6 +675,61 @@ SkTypeface* SkFontConfigInterfaceAndroid::nextLogicalTypeface(SkFontID currFontI
     return SkSafeRef(nextLogicalTypeface);
 }
 
+SkTypeface* SkFontConfigInterfaceAndroid::getTypefaceForGlyphID(uint16_t glyphID,
+                                                                const SkTypeface* origTypeface,
+                                                                const SkPaintOptionsAndroid& opts,
+                                                                int* lBounds, int* uBounds) {
+    // If we aren't using fallbacks then we shouldn't be calling this
+    SkASSERT(opts.isUsingFontFallbacks());
+    SkASSERT(origTypeface);
+
+    SkTypeface* currentTypeface = NULL;
+    int lowerBounds = 0; //inclusive
+    int upperBounds = origTypeface->countGlyphs(); //exclusive
+
+    // check to see if the glyph is in the bounds of the origTypeface
+    if (glyphID < upperBounds) {
+        currentTypeface = const_cast<SkTypeface*>(origTypeface);
+    } else {
+        FallbackFontList* currentFallbackList = findFallbackFontList(opts.getLanguage());
+        SkASSERT(currentFallbackList);
+
+        // If an object is set to prefer "kDefault_Variant" it means they have no preference
+        // In this case, we set the value to "kCompact_Variant"
+        SkPaintOptionsAndroid::FontVariant variant = opts.getFontVariant();
+        if (variant == SkPaintOptionsAndroid::kDefault_Variant) {
+            variant = SkPaintOptionsAndroid::kCompact_Variant;
+        }
+
+        int32_t acceptedVariants = SkPaintOptionsAndroid::kDefault_Variant | variant;
+        SkTypeface::Style origStyle = origTypeface->style();
+
+        for (int x = 0; x < currentFallbackList->count(); ++x) {
+            const FamilyRecID familyRecID = currentFallbackList->getAt(x);
+            const SkPaintOptionsAndroid& familyOptions = fFontFamilies[familyRecID].fPaintOptions;
+            if ((familyOptions.getFontVariant() & acceptedVariants) != 0) {
+                FontRecID matchedFont = find_best_style(fFontFamilies[familyRecID], origStyle);
+                currentTypeface = this->getTypefaceForFontRec(matchedFont);
+                lowerBounds = upperBounds;
+                upperBounds += currentTypeface->countGlyphs();
+                if (glyphID < upperBounds) {
+                    break;
+                }
+            }
+        }
+    }
+
+    if (NULL != currentTypeface) {
+        if (lBounds) {
+            *lBounds = lowerBounds;
+        }
+        if (uBounds) {
+            *uBounds = upperBounds;
+        }
+    }
+    return currentTypeface;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 bool SkGetFallbackFamilyNameForChar(SkUnichar uni, SkString* name) {
@@ -704,6 +762,14 @@ SkTypeface* SkAndroidNextLogicalTypeface(SkFontID currFontID, SkFontID origFontI
 
 }
 
+SkTypeface* SkGetTypefaceForGlyphID(uint16_t glyphID, const SkTypeface* origTypeface,
+                                    const SkPaintOptionsAndroid& options,
+                                    int* lowerBounds, int* upperBounds) {
+    SkFontConfigInterfaceAndroid* fontConfig = getSingletonInterface();
+    return fontConfig->getTypefaceForGlyphID(glyphID, origTypeface, options,
+                                             lowerBounds, upperBounds);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK