PDF: add support for named destinations
authorepoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 6 Mar 2013 00:05:13 +0000 (00:05 +0000)
committerepoger@google.com <epoger@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Wed, 6 Mar 2013 00:05:13 +0000 (00:05 +0000)
Imported from https://codereview.appspot.com/7374052/ on behalf of dml@google.com
Review URL: https://codereview.chromium.org/12466008

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

16 files changed:
gm/internal_links.cpp [new file with mode: 0644]
gyp/gmslides.gypi
include/core/SkAnnotation.h
include/pdf/SkPDFDevice.h
src/core/SkAnnotation.cpp
src/core/SkDevice.cpp
src/gpu/SkGpuDevice.cpp
src/pdf/SkPDFDevice.cpp
src/pdf/SkPDFDocument.cpp
src/pdf/SkPDFPage.cpp
src/pdf/SkPDFPage.h
src/pipe/SkGPipePriv.h
src/pipe/SkGPipeRead.cpp
src/pipe/SkGPipeWrite.cpp
src/ports/SkGlobalInitialization_default.cpp
tests/AnnotationTest.cpp

diff --git a/gm/internal_links.cpp b/gm/internal_links.cpp
new file mode 100644 (file)
index 0000000..f9846e4
--- /dev/null
@@ -0,0 +1,77 @@
+
+/*
+ * 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"
+
+#include "SkAnnotation.h"
+#include "SkData.h"
+
+namespace skiagm {
+
+/** Draws two rectangles. In output formats that support internal links (PDF),
+ *  clicking the one labeled "Link to A" should take you to the one labeled
+ *  "Target A". Note that you'll need to zoom your PDF viewer in a fair bit in
+ *  order for the scrolling to not be blocked by the edge of the document.
+ */
+class InternalLinksGM : public GM {
+public:
+    InternalLinksGM() {
+        this->setBGColor(0xFFDDDDDD);
+    }
+
+protected:
+    virtual SkString onShortName() {
+        return SkString("internal_links");
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(700, 500);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        SkAutoTUnref<SkData> name(SkData::NewWithCString("target-a"));
+
+        canvas->save();
+        canvas->translate(SkIntToScalar(100), SkIntToScalar(100));
+        drawLabeledRect(canvas, "Link to A", 0, 0);
+        SkRect rect = SkRect::MakeXYWH(0, 0, SkIntToScalar(50), SkIntToScalar(20));
+        SkAnnotateLinkToDestination(canvas, rect, name.get());
+        canvas->restore();
+
+        canvas->save();
+        canvas->translate(SkIntToScalar(200), SkIntToScalar(200));
+        SkPoint point = SkPoint::Make(SkIntToScalar(100), SkIntToScalar(50));
+        drawLabeledRect(canvas, "Target A", point.x(), point.y());
+        SkAnnotateNamedDestination(canvas, point, name.get());
+        canvas->restore();
+    }
+
+private:
+    /** Draw an arbitrary rectangle at a given location and label it with some
+     *  text. */
+    void drawLabeledRect(SkCanvas* canvas, const char* text, int x, int y) {
+        SkPaint paint;
+        paint.setColor(SK_ColorBLUE);
+        SkRect rect = SkRect::MakeXYWH(SkIntToScalar(x), SkIntToScalar(y),
+                                       SkIntToScalar(50), SkIntToScalar(20));
+        canvas->drawRect(rect, paint);
+
+        paint.setAntiAlias(true);
+        paint.setTextSize(SkIntToScalar(25));
+        paint.setColor(SK_ColorBLACK);
+        canvas->drawText(text, strlen(text), x, y, paint);
+    }
+
+    typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return SkNEW(InternalLinksGM); }
+static GMRegistry reg(MyFactory);
+
+}
index 0b42eed..ae36de0 100644 (file)
@@ -50,6 +50,7 @@
     '../gm/image.cpp',
     '../gm/imagefiltersbase.cpp',
     '../gm/imagefiltersgraph.cpp',
+    '../gm/internal_links.cpp',
     '../gm/lcdtext.cpp',
     '../gm/linepaths.cpp',
     '../gm/matrixconvolution.cpp',
index e1ecf6c..bb7f6c4 100644 (file)
@@ -12,6 +12,7 @@
 
 class SkData;
 class SkDataSet;
+class SkPoint;
 class SkStream;
 class SkWStream;
 
@@ -64,6 +65,18 @@ public:
      *  Returns the canonical key whose payload is a URL
      */
     static const char* URL_Key();
+
+    /**
+     *  Returns the canonical key whose payload is the name of a destination to
+     *  be defined.
+     */
+    static const char* Define_Named_Dest_Key();
+
+    /**
+     *  Returns the canonical key whose payload is the name of a destination to
+     *  be linked to.
+     */
+    static const char* Link_Named_Dest_Key();
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -86,4 +99,30 @@ class SkCanvas;
  */
 SK_API void SkAnnotateRectWithURL(SkCanvas*, const SkRect&, SkData*);
 
+/**
+ *  Experimental!
+ *
+ *  Annotate the canvas by associating a name with the specified point.
+ *
+ *  If the backend of this canvas does not support annotations, this call is
+ *  safely ignored.
+ *
+ *  The caller is responsible for managing its ownership of the SkData.
+ */
+SK_API void SkAnnotateNamedDestination(SkCanvas*, const SkPoint&, SkData*);
+
+/**
+ *  Experimental!
+ *
+ *  Annotate the canvas by making the specified rectangle link to a named
+ *  destination.
+ *
+ *  If the backend of this canvas does not support annotations, this call is
+ *  safely ignored.
+ *
+ *  The caller is responsible for managing its ownership of the SkData.
+ */
+SK_API void SkAnnotateLinkToDestination(SkCanvas*, const SkRect&, SkData*);
+
+
 #endif
index a13d617..90379f9 100644 (file)
@@ -17,6 +17,7 @@
 #include "SkRect.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
+#include "SkTDArray.h"
 #include "SkTScopedPtr.h"
 
 class SkPDFArray;
@@ -33,6 +34,7 @@ class SkPDFStream;
 // Private classes.
 struct ContentEntry;
 struct GraphicStateEntry;
+struct NamedDestination;
 
 /** \class SkPDFDevice
 
@@ -142,6 +144,12 @@ public:
      */
     SK_API const SkTDArray<SkPDFFont*>& getFontResources() const;
 
+    /** Add our named destinations to the supplied dictionary.
+     *  @param dict  Dictionary to add destinations to.
+     *  @param page  The PDF object representing the page for this device.
+     */
+    void appendDestinations(SkPDFDict* dict, SkPDFObject* page);
+
     /** Returns a copy of the media box for this device. The caller is required
      *  to unref() this when it is finished.
      */
@@ -191,6 +199,7 @@ private:
     SkRegion fExistingClipRegion;
     SkPDFArray* fAnnotations;
     SkPDFDict* fResourceDict;
+    SkTDArray<NamedDestination*> fNamedDestinations;
 
     SkTDArray<SkPDFGraphicState*> fGraphicStateResources;
     SkTDArray<SkPDFObject*> fXObjectResources;
@@ -273,8 +282,17 @@ private:
      */
     void copyContentEntriesToData(ContentEntry* entry, SkWStream* data) const;
 
-    bool handleAnnotations(const SkRect& r, const SkMatrix& matrix,
-                           const SkPaint& paint);
+    bool handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
+                              const SkPaint& paint);
+    bool handlePointAnnotation(const SkPoint* points, size_t count,
+                               const SkMatrix& matrix, const SkPaint& paint);
+    SkPDFDict* createLinkAnnotation(const SkRect& r, const SkMatrix& matrix);
+    void handleLinkToURL(SkData* urlData, const SkRect& r,
+                         const SkMatrix& matrix);
+    void handleLinkToNamedDest(SkData* nameData, const SkRect& r,
+                               const SkMatrix& matrix);
+    void defineNamedDestination(SkData* nameData, const SkPoint& point,
+                                const SkMatrix& matrix);
 
     typedef SkDevice INHERITED;
 };
index 5e4363e..52fa9b7 100644 (file)
@@ -8,6 +8,7 @@
 #include "SkAnnotation.h"
 #include "SkDataSet.h"
 #include "SkFlattenableBuffers.h"
+#include "SkPoint.h"
 #include "SkStream.h"
 
 SkAnnotation::SkAnnotation(SkDataSet* data, uint32_t flags) {
@@ -42,23 +43,50 @@ const char* SkAnnotationKeys::URL_Key() {
     return "SkAnnotationKey_URL";
 };
 
+const char* SkAnnotationKeys::Define_Named_Dest_Key() {
+    return "SkAnnotationKey_Define_Named_Dest";
+};
+
+const char* SkAnnotationKeys::Link_Named_Dest_Key() {
+    return "SkAnnotationKey_Link_Named_Dest";
+};
+
 ///////////////////////////////////////////////////////////////////////////////
 
 #include "SkCanvas.h"
 
-void SkAnnotateRectWithURL(SkCanvas* canvas, const SkRect& rect, SkData* value) {
-    if (NULL == value) {
-        return;
-    }
-
-    const char* key = SkAnnotationKeys::URL_Key();
+static void annotate_paint(SkPaint& paint, const char* key, SkData* value) {
     SkAutoTUnref<SkDataSet> dataset(SkNEW_ARGS(SkDataSet, (key, value)));
     SkAnnotation* ann = SkNEW_ARGS(SkAnnotation, (dataset,
                                                   SkAnnotation::kNoDraw_Flag));
 
-    SkPaint paint;
     paint.setAnnotation(ann)->unref();
     SkASSERT(paint.isNoDrawAnnotation());
+}
 
+void SkAnnotateRectWithURL(SkCanvas* canvas, const SkRect& rect, SkData* value) {
+    if (NULL == value) {
+        return;
+    }
+    SkPaint paint;
+    annotate_paint(paint, SkAnnotationKeys::URL_Key(), value);
+    canvas->drawRect(rect, paint);
+}
+
+void SkAnnotateNamedDestination(SkCanvas* canvas, const SkPoint& point, SkData* name) {
+    if (NULL == name) {
+        return;
+    }
+    SkPaint paint;
+    annotate_paint(paint, SkAnnotationKeys::Define_Named_Dest_Key(), name);
+    canvas->drawPoint(point.x(), point.y(), paint);
+}
+
+void SkAnnotateLinkToDestination(SkCanvas* canvas, const SkRect& rect, SkData* name) {
+    if (NULL == name) {
+        return;
+    }
+    SkPaint paint;
+    annotate_paint(paint, SkAnnotationKeys::Link_Named_Dest_Key(), name);
     canvas->drawRect(rect, paint);
 }
index 78ce0f6..90d4186 100644 (file)
@@ -348,6 +348,7 @@ void SkDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) {
 
 void SkDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
                           const SkPoint pts[], const SkPaint& paint) {
+    CHECK_FOR_NODRAW_ANNOTATION(paint);
     draw.drawPoints(mode, count, pts, paint);
 }
 
index 82c2691..77c4bd1 100644 (file)
@@ -597,6 +597,7 @@ static const GrPrimitiveType gPointMode2PrimtiveType[] = {
 
 void SkGpuDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode,
                              size_t count, const SkPoint pts[], const SkPaint& paint) {
+    CHECK_FOR_NODRAW_ANNOTATION(paint);
     CHECK_SHOULD_DRAW(draw, false);
 
     SkScalar width = paint.getStrokeWidth();
index eae7ab7..2452996 100644 (file)
@@ -645,6 +645,7 @@ void SkPDFDevice::cleanUp(bool clearFontUsage) {
     fShaderResources.unrefAll();
     SkSafeUnref(fAnnotations);
     SkSafeUnref(fResourceDict);
+    fNamedDestinations.deleteAll();
 
     if (clearFontUsage) {
         fFontGlyphUsage->reset();
@@ -703,6 +704,10 @@ void SkPDFDevice::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
         return;
     }
 
+    if (handlePointAnnotation(points, count, *d.fMatrix, passedPaint)) {
+        return;
+    }
+
     // SkDraw::drawPoints converts to multiple calls to fDevice->drawPath.
     // We only use this when there's a path effect because of the overhead
     // of multiple calls to setUpContentEntry it causes.
@@ -791,7 +796,7 @@ void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
         return;
     }
 
-    if (handleAnnotations(r, *d.fMatrix, paint)) {
+    if (handleRectAnnotation(r, *d.fMatrix, paint)) {
         return;
     }
 
@@ -847,7 +852,7 @@ void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& origPath,
         return;
     }
 
-    if (handleAnnotations(pathPtr->getBounds(), *d.fMatrix, paint)) {
+    if (handleRectAnnotation(pathPtr->getBounds(), *d.fMatrix, paint)) {
         return;
     }
 
@@ -1286,19 +1291,43 @@ SkData* SkPDFDevice::copyContentToData() const {
     return data.copyToData();
 }
 
-bool SkPDFDevice::handleAnnotations(const SkRect& r, const SkMatrix& matrix,
-                                    const SkPaint& p) {
+bool SkPDFDevice::handleRectAnnotation(const SkRect& r, const SkMatrix& matrix,
+                                       const SkPaint& p) {
     SkAnnotation* annotationInfo = p.getAnnotation();
     if (!annotationInfo) {
         return false;
     }
     SkData* urlData = annotationInfo->find(SkAnnotationKeys::URL_Key());
-    if (!urlData) {
+    if (urlData) {
+        handleLinkToURL(urlData, r, matrix);
+        return p.isNoDrawAnnotation();
+    }
+    SkData* linkToName = annotationInfo->find(SkAnnotationKeys::Link_Named_Dest_Key());
+    if (linkToName) {
+        handleLinkToNamedDest(linkToName, r, matrix);
+        return p.isNoDrawAnnotation();
+    }
+    return false;
+}
+
+bool SkPDFDevice::handlePointAnnotation(const SkPoint* points, size_t count,
+                                        const SkMatrix& matrix,
+                                        const SkPaint& paint) {
+    SkAnnotation* annotationInfo = paint.getAnnotation();
+    if (!annotationInfo) {
         return false;
     }
+    SkData* nameData = annotationInfo->find(SkAnnotationKeys::Define_Named_Dest_Key());
+    if (nameData) {
+        for (size_t i = 0; i < count; i++) {
+            defineNamedDestination(nameData, points[i], matrix);
+        }
+        return paint.isNoDrawAnnotation();
+    }
+    return false;
+}
 
-    SkString url(static_cast<const char *>(urlData->data()),
-                 urlData->size() - 1);
+SkPDFDict* SkPDFDevice::createLinkAnnotation(const SkRect& r, const SkMatrix& matrix) {
     SkMatrix transform = matrix;
     transform.postConcat(fInitialTransform);
     SkRect translatedRect;
@@ -1307,18 +1336,18 @@ bool SkPDFDevice::handleAnnotations(const SkRect& r, const SkMatrix& matrix,
     if (NULL == fAnnotations) {
         fAnnotations = SkNEW(SkPDFArray);
     }
-    SkAutoTUnref<SkPDFDict> annotation(new SkPDFDict("Annot"));
+    SkPDFDict* annotation(SkNEW_ARGS(SkPDFDict, ("Annot")));
     annotation->insertName("Subtype", "Link");
-    fAnnotations->append(annotation.get());
+    fAnnotations->append(annotation);
 
-    SkAutoTUnref<SkPDFArray> border(new SkPDFArray);
+    SkAutoTUnref<SkPDFArray> border(SkNEW(SkPDFArray));
     border->reserve(3);
     border->appendInt(0);  // Horizontal corner radius.
     border->appendInt(0);  // Vertical corner radius.
     border->appendInt(0);  // Width, 0 = no border.
     annotation->insert("Border", border.get());
 
-    SkAutoTUnref<SkPDFArray> rect(new SkPDFArray);
+    SkAutoTUnref<SkPDFArray> rect(SkNEW(SkPDFArray));
     rect->reserve(4);
     rect->appendScalar(translatedRect.fLeft);
     rect->appendScalar(translatedRect.fTop);
@@ -1326,12 +1355,66 @@ bool SkPDFDevice::handleAnnotations(const SkRect& r, const SkMatrix& matrix,
     rect->appendScalar(translatedRect.fBottom);
     annotation->insert("Rect", rect.get());
 
-    SkAutoTUnref<SkPDFDict> action(new SkPDFDict("Action"));
+    return annotation;
+}
+
+void SkPDFDevice::handleLinkToURL(SkData* urlData, const SkRect& r,
+                                  const SkMatrix& matrix) {
+    SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
+
+    SkString url(static_cast<const char *>(urlData->data()),
+                 urlData->size() - 1);
+    SkAutoTUnref<SkPDFDict> action(SkNEW_ARGS(SkPDFDict, ("Action")));
     action->insertName("S", "URI");
-    action->insert("URI", new SkPDFString(url))->unref();
+    action->insert("URI", SkNEW_ARGS(SkPDFString, (url)))->unref();
     annotation->insert("A", action.get());
+}
+
+void SkPDFDevice::handleLinkToNamedDest(SkData* nameData, const SkRect& r,
+                                        const SkMatrix& matrix) {
+    SkAutoTUnref<SkPDFDict> annotation(createLinkAnnotation(r, matrix));
+    SkString name(static_cast<const char *>(nameData->data()),
+                  nameData->size() - 1);
+    annotation->insert("Dest", SkNEW_ARGS(SkPDFString, (name)))->unref();
+}
+
+struct NamedDestination {
+    const SkData* nameData;
+    SkPoint point;
 
-    return p.isNoDrawAnnotation();
+    NamedDestination(const SkData* nameData, const SkPoint& point)
+        : nameData(nameData), point(point) {
+        nameData->ref();
+    }
+
+    ~NamedDestination() {
+        nameData->unref();
+    }
+};
+
+void SkPDFDevice::defineNamedDestination(SkData* nameData, const SkPoint& point,
+                                         const SkMatrix& matrix) {
+    SkMatrix transform = matrix;
+    transform.postConcat(fInitialTransform);
+    SkPoint translatedPoint;
+    transform.mapXY(point.x(), point.y(), &translatedPoint);
+    fNamedDestinations.push(
+        SkNEW_ARGS(NamedDestination, (nameData, translatedPoint)));
+}
+
+void SkPDFDevice::appendDestinations(SkPDFDict* dict, SkPDFObject* page) {
+    int nDest = fNamedDestinations.count();
+    for (int i = 0; i < nDest; i++) {
+        NamedDestination* dest = fNamedDestinations[i];
+        SkAutoTUnref<SkPDFArray> pdfDest(SkNEW(SkPDFArray));
+        pdfDest->reserve(5);
+        pdfDest->append(SkNEW_ARGS(SkPDFObjRef, (page)))->unref();
+        pdfDest->appendName("XYZ");
+        pdfDest->appendInt(dest->point.x());
+        pdfDest->appendInt(dest->point.y());
+        pdfDest->appendInt(0);  // Leave zoom unchanged
+        dict->insert(static_cast<const char *>(dest->nameData->data()), pdfDest);
+    }
 }
 
 SkPDFFormXObject* SkPDFDevice::createFormXObjectFromDevice() {
index 4c66c6b..7ee2778 100644 (file)
@@ -107,18 +107,25 @@ bool SkPDFDocument::emitPDF(SkWStream* stream) {
         fDocCatalog->insert("OutputIntent", intentArray.get());
         */
 
+        SkPDFDict* dests = SkNEW(SkPDFDict);  // fPageResources owns reference
+        fCatalog->addObject(dests, true /* onFirstPage */);
+        fPageResources.push(dests);
+
         bool firstPage = true;
         for (int i = 0; i < fPages.count(); i++) {
             int resourceCount = fPageResources.count();
             fPages[i]->finalizePage(fCatalog.get(), firstPage, &fPageResources);
             addResourcesToCatalog(resourceCount, firstPage, &fPageResources,
                                   fCatalog.get());
+            fPages[i]->appendDestinations(dests);
             if (i == 0) {
                 firstPage = false;
                 fSecondPageFirstResourceIndex = fPageResources.count();
             }
         }
 
+        fDocCatalog->insert("Dests", SkNEW_ARGS(SkPDFObjRef, (dests)))->unref();
+
         // Build font subsetting info before proceeding.
         perform_font_subsetting(fCatalog.get(), fPages, &fSubstitutes);
 
index f47f8ff..4cdc000 100644 (file)
@@ -147,3 +147,7 @@ const SkTDArray<SkPDFFont*>& SkPDFPage::getFontResources() const {
 const SkPDFGlyphSetMap& SkPDFPage::getFontGlyphUsage() const {
     return fDevice->getFontGlyphUsage();
 }
+
+void SkPDFPage::appendDestinations(SkPDFDict* dict) {
+    fDevice->appendDestinations(dict, this);
+}
index 72ba335..285a2f5 100644 (file)
@@ -48,6 +48,11 @@ public:
     void finalizePage(SkPDFCatalog* catalog, bool firstPage,
                       SkTDArray<SkPDFObject*>* resourceObjects);
 
+    /** Add destinations for this page to the supplied dictionary.
+     *  @param dict       Dictionary to add destinations to.
+     */
+    void appendDestinations(SkPDFDict* dict);
+
     /** Determine the size of the page content and store to the catalog
      *  the offsets of all nonresource-indirect objects that make up the page
      *  content.  This must be called before emitPage(), but after finalizePage.
index b563652..f5f98f2 100644 (file)
@@ -25,8 +25,9 @@ enum PaintFlats {
     kShader_PaintFlat,
     kImageFilter_PaintFlat,
     kXfermode_PaintFlat,
+    kAnnotation_PaintFlat,
 
-    kLast_PaintFlat = kXfermode_PaintFlat
+    kLast_PaintFlat = kAnnotation_PaintFlat
 };
 #define kCount_PaintFlats   (kLast_PaintFlat + 1)
 
index 6cffb1b..f47f3bf 100644 (file)
@@ -53,6 +53,9 @@ static void set_paintflat(SkPaint* paint, SkFlattenable* obj, unsigned paintFlat
         case kXfermode_PaintFlat:
             paint->setXfermode((SkXfermode*)obj);
             break;
+        case kAnnotation_PaintFlat:
+            paint->setAnnotation((SkAnnotation*)obj);
+            break;
         default:
             SkDEBUGFAIL("never gets here");
     }
index cfd1e7c..34020df 100644 (file)
@@ -6,6 +6,7 @@
  * found in the LICENSE file.
  */
 
+#include "SkAnnotation.h"
 #include "SkBitmapHeap.h"
 #include "SkCanvas.h"
 #include "SkColorFilter.h"
@@ -47,6 +48,7 @@ static SkFlattenable* get_paintflat(const SkPaint& paint, unsigned paintFlat) {
         case kShader_PaintFlat:         return paint.getShader();
         case kImageFilter_PaintFlat:    return paint.getImageFilter();
         case kXfermode_PaintFlat:       return paint.getXfermode();
+        case kAnnotation_PaintFlat:     return paint.getAnnotation();
     }
     SkDEBUGFAIL("never gets here");
     return NULL;
index 26a61ca..2d35cef 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "Sk1DPathEffect.h"
 #include "Sk2DPathEffect.h"
+#include "SkAnnotation.h"
 #include "SkAvoidXfermode.h"
 #include "SkBicubicImageFilter.h"
 #include "SkBitmapSource.h"
@@ -30,6 +31,8 @@
 #include "SkComposeShader.h"
 #include "SkCornerPathEffect.h"
 #include "SkDashPathEffect.h"
+#include "SkData.h"
+#include "SkDataSet.h"
 #include "SkDiscretePathEffect.h"
 #include "SkDisplacementMapEffect.h"
 #include "SkEmptyShader.h"
@@ -52,6 +55,7 @@
 
 void SkFlattenable::InitializeFlattenables() {
 
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkAnnotation)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkAvoidXfermode)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBicubicImageFilter)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBitmapProcShader)
@@ -66,6 +70,8 @@ void SkFlattenable::InitializeFlattenables() {
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkComposeShader)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkCornerPathEffect)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDashPathEffect)
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkData)
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDataSet)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDilateImageFilter)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDiscretePathEffect)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDisplacementMapEffect)
index 586525a..5429b58 100644 (file)
 #include "SkPDFDevice.h"
 #include "SkPDFDocument.h"
 
+/** Returns true if data (may contain null characters) contains needle (null
+ *  terminated). */
+static bool ContainsString(const char* data, size_t dataSize, const char* needle) {
+    size_t nSize = strlen(needle);
+    for (size_t i = 0; i < dataSize - nSize; i++) {
+        if (strncmp(&data[i], needle, nSize) == 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
 static void test_nodraw(skiatest::Reporter* reporter) {
     SkBitmap bm;
     bm.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
@@ -55,27 +67,38 @@ static void test_pdf_link_annotations(skiatest::Reporter* reporter) {
         SkAutoDataUnref out(outStream.copyToData());
         const char* rawOutput = (const char*)out->data();
 
-        bool found = false;
-        for (size_t i = 0; i < out->size() - 8; i++) {
-            if (rawOutput[i + 0] == '/' &&
-                rawOutput[i + 1] == 'A' &&
-                rawOutput[i + 2] == 'n' &&
-                rawOutput[i + 3] == 'n' &&
-                rawOutput[i + 4] == 'o' &&
-                rawOutput[i + 5] == 't' &&
-                rawOutput[i + 6] == 's' &&
-                rawOutput[i + 7] == ' ') {
-                found = true;
-                break;
-            }
-        }
-        REPORTER_ASSERT(reporter, found == tests[testNum].expectAnnotations);
+        REPORTER_ASSERT(reporter,
+            ContainsString(rawOutput, out->size(), "/Annots ")
+            == tests[testNum].expectAnnotations);
     }
 }
 
+static void test_named_destination_annotations(skiatest::Reporter* reporter) {
+    SkISize size = SkISize::Make(612, 792);
+    SkMatrix initialTransform;
+    initialTransform.reset();
+    SkPDFDevice device(size, size, initialTransform);
+    SkCanvas canvas(&device);
+
+    SkPoint p = SkPoint::Make(SkIntToScalar(72), SkIntToScalar(72));
+    SkAutoDataUnref data(SkData::NewWithCString("example"));
+    SkAnnotateNamedDestination(&canvas, p, data.get());
+
+    SkPDFDocument doc;
+    doc.appendPage(&device);
+    SkDynamicMemoryWStream outStream;
+    doc.emitPDF(&outStream);
+    SkAutoDataUnref out(outStream.copyToData());
+    const char* rawOutput = (const char*)out->data();
+
+    REPORTER_ASSERT(reporter,
+        ContainsString(rawOutput, out->size(), "/example "));
+}
+
 static void TestAnnotation(skiatest::Reporter* reporter) {
     test_nodraw(reporter);
     test_pdf_link_annotations(reporter);
+    test_named_destination_annotations(reporter);
 }
 
 #include "TestClassDef.h"