SkPDF: fall back on paths for unembeddable fonts.
authorhalcanary <halcanary@google.com>
Mon, 12 Oct 2015 20:05:04 +0000 (13:05 -0700)
committerCommit bot <commit-bot@chromium.org>
Mon, 12 Oct 2015 20:05:04 +0000 (13:05 -0700)
Add GM, SkPDFFont::CanEmbedTypeface

BUG=skia:3866

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

gm/pdf_never_embed.cpp [new file with mode: 0644]
resources/fonts/Roboto2-Regular_NoEmbed.ttf [new file with mode: 0644]
src/pdf/SkPDFCanon.h
src/pdf/SkPDFDevice.cpp
src/pdf/SkPDFDevice.h
src/pdf/SkPDFFont.cpp
src/pdf/SkPDFFont.h
tests/PDFPrimitivesTest.cpp

diff --git a/gm/pdf_never_embed.cpp b/gm/pdf_never_embed.cpp
new file mode 100644 (file)
index 0000000..ef7974e
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Resources.h"
+#include "SkTypeface.h"
+#include "gm.h"
+
+static void excercise_draw_pos_text(SkCanvas* canvas,
+                                    const char* text,
+                                    SkScalar x, SkScalar y,
+                                    const SkPaint& paint) {
+    size_t textLen = strlen(text);
+    SkAutoTArray<SkScalar> widths(SkToInt(textLen));
+    paint.getTextWidths(text, textLen, &widths[0]);
+    SkAutoTArray<SkPoint> pos(SkToInt(textLen));
+    for (int i = 0; i < SkToInt(textLen); ++i) {
+        pos[i].set(x, y);
+        x += widths[i];
+    }
+    canvas->drawPosText(text, textLen, &pos[0], paint);
+}
+
+DEF_SIMPLE_GM(pdf_never_embed, canvas, 512, 512) {
+    const char resource[] = "fonts/Roboto2-Regular_NoEmbed.ttf";
+    SkAutoTUnref<SkTypeface> typeface(GetResourceAsTypeface(resource));
+    if (!typeface) {
+        return;
+    }
+    SkPaint p;
+    p.setTextSize(60);
+    p.setTypeface(typeface);
+    p.setAntiAlias(true);
+
+    const char text[] = "HELLO, WORLD!";
+
+    canvas->drawColor(SK_ColorWHITE);
+    excercise_draw_pos_text(canvas, text, 30, 90, p);
+
+    canvas->save();
+    canvas->rotate(45.0f);
+    p.setColor(0xF0800000);
+    excercise_draw_pos_text(canvas, text, 30, 45, p);
+    canvas->restore();
+
+    canvas->save();
+    canvas->scale(1, 4.0);
+    p.setColor(0xF0008000);
+    excercise_draw_pos_text(canvas, text, 15, 70, p);
+    canvas->restore();
+
+    canvas->scale(1.0, 0.5);
+    p.setColor(0xF0000080);
+    canvas->drawText(text, strlen(text), 30, 700, p);
+}
diff --git a/resources/fonts/Roboto2-Regular_NoEmbed.ttf b/resources/fonts/Roboto2-Regular_NoEmbed.ttf
new file mode 100644 (file)
index 0000000..0892862
Binary files /dev/null and b/resources/fonts/Roboto2-Regular_NoEmbed.ttf differ
index a55024d..988b285 100644 (file)
@@ -78,6 +78,8 @@ public:
     void addPDFBitmap(uint32_t imageUniqueID, SkPDFObject*);
     const SkImage* bitmapToImage(const SkBitmap&);
 
+    SkTHashMap<uint32_t, bool> fCanEmbedTypeface;
+
 private:
     struct FontRec {
         SkPDFFont* fFont;
index 9134d80..c27cc64 100644 (file)
@@ -1231,8 +1231,57 @@ static SkString format_wide_string(const uint16_t* input,
     }
 }
 
+static void draw_transparent_text(SkPDFDevice* device,
+                                  const SkDraw& d,
+                                  const void* text, size_t len,
+                                  SkScalar x, SkScalar y,
+                                  const SkPaint& srcPaint) {
+
+    SkPaint transparent;
+    if (!SkPDFFont::CanEmbedTypeface(transparent.getTypeface(),
+                                     device->getCanon())) {
+        SkDEBUGFAIL("default typeface should be embeddable");
+        return;  // Avoid infinite loop in release.
+    }
+    transparent.setTextSize(srcPaint.getTextSize());
+    transparent.setColor(SK_ColorTRANSPARENT);
+    switch (srcPaint.getTextEncoding()) {
+        case SkPaint::kGlyphID_TextEncoding: {
+            // Since a glyphId<->Unicode mapping is typeface-specific,
+            // map back to Unicode first.
+            size_t glyphCount = len / 2;
+            SkAutoTMalloc<SkUnichar> unichars(glyphCount);
+            srcPaint.glyphsToUnichars(
+                    (const uint16_t*)text, SkToInt(glyphCount), &unichars[0]);
+            transparent.setTextEncoding(SkPaint::kUTF32_TextEncoding);
+            device->drawText(d, &unichars[0],
+                             glyphCount * sizeof(SkUnichar),
+                             x, y, transparent);
+            break;
+        }
+        case SkPaint::kUTF8_TextEncoding:
+        case SkPaint::kUTF16_TextEncoding:
+        case SkPaint::kUTF32_TextEncoding:
+            transparent.setTextEncoding(srcPaint.getTextEncoding());
+            device->drawText(d, text, len, x, y, transparent);
+            break;
+        default:
+            SkFAIL("unknown text encoding");
+    }
+}
+
+
 void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
                            SkScalar x, SkScalar y, const SkPaint& srcPaint) {
+    if (!SkPDFFont::CanEmbedTypeface(srcPaint.getTypeface(), fCanon)) {
+        // http://skbug.com/3866
+        SkPath path;
+        srcPaint.getTextPath(text, len, x, y, &path);
+        this->drawPath(d, path, srcPaint, &SkMatrix::I(), true);
+        // Draw text transparently to make it copyable/searchable/accessable.
+        draw_transparent_text(this, d, text, len, x, y, srcPaint);
+        return;
+    }
     SkPaint paint = srcPaint;
     replace_srcmode_on_opaque_paint(&paint);
 
@@ -1285,6 +1334,29 @@ void SkPDFDevice::drawText(const SkDraw& d, const void* text, size_t len,
 void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
                               const SkScalar pos[], int scalarsPerPos,
                               const SkPoint& offset, const SkPaint& srcPaint) {
+    if (!SkPDFFont::CanEmbedTypeface(srcPaint.getTypeface(), fCanon)) {
+        const SkPoint* positions = reinterpret_cast<const SkPoint*>(pos);
+        SkAutoTMalloc<SkPoint> positionsBuffer;
+        if (2 != scalarsPerPos) {
+            int glyphCount = srcPaint.textToGlyphs(text, len, NULL);
+            positionsBuffer.reset(glyphCount);
+            for (int  i = 0; i < glyphCount; ++i) {
+                positionsBuffer[i].set(pos[i], 0.0f);
+            }
+            positions = &positionsBuffer[0];
+        }
+        SkPath path;
+        srcPaint.getPosTextPath(text, len, positions, &path);
+        SkMatrix matrix;
+        matrix.setTranslate(offset);
+        this->drawPath(d, path, srcPaint, &matrix, true);
+        // Draw text transparently to make it copyable/searchable/accessable.
+        draw_transparent_text(
+                this, d, text, len, offset.x() + positions[0].x(),
+                offset.y() + positions[0].y(), srcPaint);
+        return;
+    }
+
     SkPaint paint = srcPaint;
     replace_srcmode_on_opaque_paint(&paint);
 
index 75447f9..742e45c 100644 (file)
@@ -189,9 +189,7 @@ public:
         return *(fFontGlyphUsage.get());
     }
 
-#ifdef SK_DEBUG
     SkPDFCanon* getCanon() const { return fCanon; }
-#endif  // SK_DEBUG
 
 protected:
     const SkBitmap& onAccessBitmap() override {
index 8df59b7..9cbada8 100644 (file)
@@ -1051,10 +1051,7 @@ bool SkPDFCIDFont::addFontDescriptor(int16_t defaultWidth,
         this->insertObjRef("FontDescriptor", descriptor.detach());
         return false;
     }
-    if (!canEmbed()) {
-        this->insertObjRef("FontDescriptor", descriptor.detach());
-        return true;
-    }
+    SkASSERT(this->canEmbed());
 
     switch (getType()) {
         case SkAdvancedTypefaceMetrics::kTrueType_Font: {
@@ -1222,13 +1219,12 @@ bool SkPDFType1Font::addFontDescriptor(int16_t defaultWidth) {
     if (fontData.get() == nullptr) {
         return false;
     }
-    if (canEmbed()) {
-        SkAutoTUnref<SkPDFStream> fontStream(new SkPDFStream(fontData.get()));
-        fontStream->insertInt("Length1", header);
-        fontStream->insertInt("Length2", data);
-        fontStream->insertInt("Length3", trailer);
-        descriptor->insertObjRef("FontFile", fontStream.detach());
-    }
+    SkASSERT(this->canEmbed());
+    SkAutoTUnref<SkPDFStream> fontStream(new SkPDFStream(fontData.get()));
+    fontStream->insertInt("Length1", header);
+    fontStream->insertInt("Length2", data);
+    fontStream->insertInt("Length3", trailer);
+    descriptor->insertObjRef("FontFile", fontStream.detach());
 
     this->insertObjRef("FontDescriptor", descriptor.detach());
 
@@ -1418,3 +1414,22 @@ SkPDFFont::Match SkPDFFont::IsMatch(SkPDFFont* existingFont,
     return (existingGlyphID == searchGlyphID) ? SkPDFFont::kExact_Match
                                               : SkPDFFont::kRelated_Match;
 }
+
+//  Since getAdvancedTypefaceMetrics is expensive, cache the result.
+bool SkPDFFont::CanEmbedTypeface(SkTypeface* typeface, SkPDFCanon* canon) {
+    SkAutoResolveDefaultTypeface face(typeface);
+    uint32_t id = face->uniqueID();
+    if (bool* value = canon->fCanEmbedTypeface.find(id)) {
+        return *value;
+    }
+    bool canEmbed = true;
+    SkAutoTUnref<const SkAdvancedTypefaceMetrics> fontMetrics(
+            face->getAdvancedTypefaceMetrics(
+                    SkTypeface::kNo_PerGlyphInfo, nullptr, 0));
+    if (fontMetrics) {
+        canEmbed = !SkToBool(
+                fontMetrics->fFlags &
+                SkAdvancedTypefaceMetrics::kNotEmbeddable_FontFlag);
+    }
+    return *canon->fCanEmbedTypeface.set(id, canEmbed);
+}
index 404e8b7..ca0a51f 100644 (file)
@@ -149,6 +149,12 @@ public:
                          uint32_t searchFontID,
                          uint16_t searchGlyphID);
 
+    /**
+     *  Return false iff the typeface has its NotEmbeddable flag set.
+     *  If typeface is NULL, the default typeface is checked.
+     */
+    static bool CanEmbedTypeface(SkTypeface*, SkPDFCanon*);
+
 protected:
     // Common constructor to handle common members.
     SkPDFFont(const SkAdvancedTypefaceMetrics* fontInfo,
index 3253fca..5fce53e 100644 (file)
@@ -5,6 +5,7 @@
  * found in the LICENSE file.
  */
 
+#include "Resources.h"
 #include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkData.h"
@@ -14,6 +15,7 @@
 #include "SkMatrix.h"
 #include "SkPDFCanon.h"
 #include "SkPDFDevice.h"
+#include "SkPDFFont.h"
 #include "SkPDFStream.h"
 #include "SkPDFTypes.h"
 #include "SkReadBuffer.h"
@@ -21,6 +23,7 @@
 #include "SkStream.h"
 #include "SkTypes.h"
 #include "Test.h"
+#include "sk_tool_utils.h"
 
 #define DUMMY_TEXT "DCT compessed stream."
 
@@ -415,3 +418,20 @@ DEF_TEST(PDFImageFilter, reporter) {
     // Filter was used in rendering; should be visited.
     REPORTER_ASSERT(reporter, filter->visited());
 }
+
+// Check that PDF rendering of image filters successfully falls back to
+// CPU rasterization.
+DEF_TEST(PDFFontCanEmbedTypeface, reporter) {
+    SkPDFCanon canon;
+
+    const char resource[] = "fonts/Roboto2-Regular_NoEmbed.ttf";
+    SkAutoTUnref<SkTypeface> noEmbedTypeface(GetResourceAsTypeface(resource));
+    if (noEmbedTypeface) {
+        REPORTER_ASSERT(reporter,
+                        !SkPDFFont::CanEmbedTypeface(noEmbedTypeface, &canon));
+    }
+    SkAutoTUnref<SkTypeface> portableTypeface(
+            sk_tool_utils::create_portable_typeface(NULL, SkTypeface::kNormal));
+    REPORTER_ASSERT(reporter,
+                    SkPDFFont::CanEmbedTypeface(portableTypeface, &canon));
+}