Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright 2010 The Android Open Source Project
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkBitmap.h"
      9 #include "SkCanvas.h"
     10 #include "SkData.h"
     11 #include "SkFlate.h"
     12 #include "SkImageEncoder.h"
     13 #include "SkMatrix.h"
     14 #include "SkPDFCatalog.h"
     15 #include "SkPDFDevice.h"
     16 #include "SkPDFStream.h"
     17 #include "SkPDFTypes.h"
     18 #include "SkReadBuffer.h"
     19 #include "SkScalar.h"
     20 #include "SkStream.h"
     21 #include "SkTypes.h"
     22 #include "Test.h"
     23 
     24 class SkPDFTestDict : public SkPDFDict {
     25 public:
     26   virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
     27                             SkTSet<SkPDFObject*>* newResourceObjects) {
     28         for (int i = 0; i < fResources.count(); i++) {
     29             newResourceObjects->add(fResources[i]);
     30             fResources[i]->ref();
     31         }
     32     }
     33 
     34     void addResource(SkPDFObject* object) {
     35         fResources.append(1, &object);
     36     }
     37 
     38 private:
     39     SkTDArray<SkPDFObject*> fResources;
     40 };
     41 
     42 #define DUMMY_TEXT "DCT compessed stream."
     43 
     44 static SkData* encode_to_dct_data(size_t* pixelRefOffset, const SkBitmap& bitmap) {
     45     *pixelRefOffset = 0;
     46     return SkData::NewWithProc(DUMMY_TEXT, sizeof(DUMMY_TEXT) - 1, NULL, NULL);
     47 }
     48 
     49 static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
     50                           const void* buffer, size_t len) {
     51     SkAutoDataUnref data(stream.copyToData());
     52     if (offset + len > data->size()) {
     53         return false;
     54     }
     55     return memcmp(data->bytes() + offset, buffer, len) == 0;
     56 }
     57 
     58 static bool stream_contains(const SkDynamicMemoryWStream& stream,
     59                             const char* buffer) {
     60     SkAutoDataUnref data(stream.copyToData());
     61     int len = strlen(buffer);  // our buffer does not have EOSs.
     62 
     63     for (int offset = 0 ; offset < (int)data->size() - len; offset++) {
     64         if (memcmp(data->bytes() + offset, buffer, len) == 0) {
     65             return true;
     66         }
     67     }
     68 
     69     return false;
     70 }
     71 
     72 static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
     73                               const char* expectedData, size_t expectedSize,
     74                               bool indirect, bool compression) {
     75     SkPDFDocument::Flags docFlags = (SkPDFDocument::Flags) 0;
     76     if (!compression) {
     77         docFlags = SkTBitOr(docFlags, SkPDFDocument::kFavorSpeedOverSize_Flags);
     78     }
     79     SkPDFCatalog catalog(docFlags);
     80     size_t directSize = obj->getOutputSize(&catalog, false);
     81     REPORTER_ASSERT(reporter, directSize == expectedSize);
     82 
     83     SkDynamicMemoryWStream buffer;
     84     obj->emit(&buffer, &catalog, false);
     85     REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
     86     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedData,
     87                                             directSize));
     88 
     89     if (indirect) {
     90         // Indirect output.
     91         static char header[] = "1 0 obj\n";
     92         static size_t headerLen = strlen(header);
     93         static char footer[] = "\nendobj\n";
     94         static size_t footerLen = strlen(footer);
     95 
     96         catalog.addObject(obj, false);
     97 
     98         size_t indirectSize = obj->getOutputSize(&catalog, true);
     99         REPORTER_ASSERT(reporter,
    100                         indirectSize == directSize + headerLen + footerLen);
    101 
    102         buffer.reset();
    103         obj->emit(&buffer, &catalog, true);
    104         REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset());
    105         REPORTER_ASSERT(reporter, stream_equals(buffer, 0, header, headerLen));
    106         REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen, expectedData,
    107                                                 directSize));
    108         REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen + directSize,
    109                                                 footer, footerLen));
    110     }
    111 }
    112 
    113 static void SimpleCheckObjectOutput(skiatest::Reporter* reporter,
    114                                     SkPDFObject* obj,
    115                                     const char* expectedResult) {
    116     CheckObjectOutput(reporter, obj, expectedResult,
    117                       strlen(expectedResult), true, false);
    118 }
    119 
    120 static void TestPDFStream(skiatest::Reporter* reporter) {
    121     char streamBytes[] = "Test\nFoo\tBar";
    122     SkAutoTUnref<SkMemoryStream> streamData(new SkMemoryStream(
    123         streamBytes, strlen(streamBytes), true));
    124     SkAutoTUnref<SkPDFStream> stream(new SkPDFStream(streamData.get()));
    125     SimpleCheckObjectOutput(
    126         reporter, stream.get(),
    127         "<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream");
    128     stream->insert("Attribute", new SkPDFInt(42))->unref();
    129     SimpleCheckObjectOutput(reporter, stream.get(),
    130                             "<</Length 12\n/Attribute 42\n>> stream\n"
    131                                 "Test\nFoo\tBar\nendstream");
    132 
    133     if (SkFlate::HaveFlate()) {
    134         char streamBytes2[] = "This is a longer string, so that compression "
    135                               "can do something with it. With shorter strings, "
    136                               "the short circuit logic cuts in and we end up "
    137                               "with an uncompressed string.";
    138         SkAutoDataUnref streamData2(SkData::NewWithCopy(streamBytes2,
    139                                                         strlen(streamBytes2)));
    140         SkAutoTUnref<SkPDFStream> stream(new SkPDFStream(streamData2.get()));
    141 
    142         SkDynamicMemoryWStream compressedByteStream;
    143         SkFlate::Deflate(streamData2.get(), &compressedByteStream);
    144         SkAutoDataUnref compressedData(compressedByteStream.copyToData());
    145 
    146         // Check first without compression.
    147         SkDynamicMemoryWStream expectedResult1;
    148         expectedResult1.writeText("<</Length 167\n>> stream\n");
    149         expectedResult1.writeText(streamBytes2);
    150         expectedResult1.writeText("\nendstream");
    151         SkAutoDataUnref expectedResultData1(expectedResult1.copyToData());
    152         CheckObjectOutput(reporter, stream.get(),
    153                           (const char*) expectedResultData1->data(),
    154                           expectedResultData1->size(), true, false);
    155 
    156         // Then again with compression.
    157         SkDynamicMemoryWStream expectedResult2;
    158         expectedResult2.writeText("<</Filter /FlateDecode\n/Length 116\n"
    159                                  ">> stream\n");
    160         expectedResult2.write(compressedData->data(), compressedData->size());
    161         expectedResult2.writeText("\nendstream");
    162         SkAutoDataUnref expectedResultData2(expectedResult2.copyToData());
    163         CheckObjectOutput(reporter, stream.get(),
    164                           (const char*) expectedResultData2->data(),
    165                           expectedResultData2->size(), true, true);
    166     }
    167 }
    168 
    169 static void TestCatalog(skiatest::Reporter* reporter) {
    170     SkPDFCatalog catalog((SkPDFDocument::Flags)0);
    171     SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
    172     SkAutoTUnref<SkPDFInt> int2(new SkPDFInt(2));
    173     SkAutoTUnref<SkPDFInt> int3(new SkPDFInt(3));
    174     int1.get()->ref();
    175     SkAutoTUnref<SkPDFInt> int1Again(int1.get());
    176 
    177     catalog.addObject(int1.get(), false);
    178     catalog.addObject(int2.get(), false);
    179     catalog.addObject(int3.get(), false);
    180 
    181     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
    182     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
    183     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int3.get()) == 3);
    184 
    185     SkDynamicMemoryWStream buffer;
    186     catalog.emitObjectNumber(&buffer, int1.get());
    187     catalog.emitObjectNumber(&buffer, int2.get());
    188     catalog.emitObjectNumber(&buffer, int3.get());
    189     catalog.emitObjectNumber(&buffer, int1Again.get());
    190     char expectedResult[] = "1 02 03 01 0";
    191     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
    192                                             strlen(expectedResult)));
    193 }
    194 
    195 static void TestObjectRef(skiatest::Reporter* reporter) {
    196     SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
    197     SkAutoTUnref<SkPDFInt> int2(new SkPDFInt(2));
    198     SkAutoTUnref<SkPDFObjRef> int2ref(new SkPDFObjRef(int2.get()));
    199 
    200     SkPDFCatalog catalog((SkPDFDocument::Flags)0);
    201     catalog.addObject(int1.get(), false);
    202     catalog.addObject(int2.get(), false);
    203     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
    204     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
    205 
    206     char expectedResult[] = "2 0 R";
    207     SkDynamicMemoryWStream buffer;
    208     int2ref->emitObject(&buffer, &catalog, false);
    209     REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
    210     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
    211                                             buffer.getOffset()));
    212 }
    213 
    214 static void TestSubstitute(skiatest::Reporter* reporter) {
    215     SkAutoTUnref<SkPDFTestDict> proxy(new SkPDFTestDict());
    216     SkAutoTUnref<SkPDFTestDict> stub(new SkPDFTestDict());
    217     SkAutoTUnref<SkPDFInt> int33(new SkPDFInt(33));
    218     SkAutoTUnref<SkPDFDict> stubResource(new SkPDFDict());
    219     SkAutoTUnref<SkPDFInt> int44(new SkPDFInt(44));
    220 
    221     stub->insert("Value", int33.get());
    222     stubResource->insert("InnerValue", int44.get());
    223     stub->addResource(stubResource.get());
    224 
    225     SkPDFCatalog catalog((SkPDFDocument::Flags)0);
    226     catalog.addObject(proxy.get(), false);
    227     catalog.setSubstitute(proxy.get(), stub.get());
    228 
    229     SkDynamicMemoryWStream buffer;
    230     proxy->emit(&buffer, &catalog, false);
    231     catalog.emitSubstituteResources(&buffer, false);
    232 
    233     char objectResult[] = "2 0 obj\n<</Value 33\n>>\nendobj\n";
    234     REPORTER_ASSERT(
    235         reporter,
    236         catalog.setFileOffset(proxy.get(), 0) == strlen(objectResult));
    237 
    238     char expectedResult[] =
    239         "<</Value 33\n>>1 0 obj\n<</InnerValue 44\n>>\nendobj\n";
    240     REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
    241     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
    242                                             buffer.getOffset()));
    243 }
    244 
    245 // Create a bitmap that would be very eficiently compressed in a ZIP.
    246 static void setup_bitmap(SkBitmap* bitmap, int width, int height) {
    247     bitmap->allocN32Pixels(width, height);
    248     bitmap->eraseColor(SK_ColorWHITE);
    249 }
    250 
    251 static void TestImage(skiatest::Reporter* reporter, const SkBitmap& bitmap,
    252                       const char* expected, bool useDCTEncoder) {
    253     SkISize pageSize = SkISize::Make(bitmap.width(), bitmap.height());
    254     SkAutoTUnref<SkPDFDevice> dev(new SkPDFDevice(pageSize, pageSize, SkMatrix::I()));
    255 
    256     if (useDCTEncoder) {
    257         dev->setDCTEncoder(encode_to_dct_data);
    258     }
    259 
    260     SkCanvas c(dev);
    261     c.drawBitmap(bitmap, 0, 0, NULL);
    262 
    263     SkPDFDocument doc;
    264     doc.appendPage(dev);
    265 
    266     SkDynamicMemoryWStream stream;
    267     doc.emitPDF(&stream);
    268 
    269     REPORTER_ASSERT(reporter, stream_contains(stream, expected));
    270 }
    271 
    272 static void TestUncompressed(skiatest::Reporter* reporter) {
    273     SkBitmap bitmap;
    274     setup_bitmap(&bitmap, 1, 1);
    275     TestImage(reporter, bitmap,
    276               "/Subtype /Image\n"
    277               "/Width 1\n"
    278               "/Height 1\n"
    279               "/ColorSpace /DeviceRGB\n"
    280               "/BitsPerComponent 8\n"
    281               "/Length 3\n"
    282               ">> stream",
    283               true);
    284 }
    285 
    286 static void TestFlateDecode(skiatest::Reporter* reporter) {
    287     if (!SkFlate::HaveFlate()) {
    288         return;
    289     }
    290     SkBitmap bitmap;
    291     setup_bitmap(&bitmap, 10, 10);
    292     TestImage(reporter, bitmap,
    293               "/Subtype /Image\n"
    294               "/Width 10\n"
    295               "/Height 10\n"
    296               "/ColorSpace /DeviceRGB\n"
    297               "/BitsPerComponent 8\n"
    298               "/Filter /FlateDecode\n"
    299               "/Length 13\n"
    300               ">> stream",
    301               false);
    302 }
    303 
    304 static void TestDCTDecode(skiatest::Reporter* reporter) {
    305     SkBitmap bitmap;
    306     setup_bitmap(&bitmap, 32, 32);
    307     TestImage(reporter, bitmap,
    308               "/Subtype /Image\n"
    309               "/Width 32\n"
    310               "/Height 32\n"
    311               "/ColorSpace /DeviceRGB\n"
    312               "/BitsPerComponent 8\n"
    313               "/Filter /DCTDecode\n"
    314               "/ColorTransform 0\n"
    315               "/Length 21\n"
    316               ">> stream",
    317               true);
    318 }
    319 
    320 static void TestImages(skiatest::Reporter* reporter) {
    321     TestUncompressed(reporter);
    322     TestFlateDecode(reporter);
    323     TestDCTDecode(reporter);
    324 }
    325 
    326 // This test used to assert without the fix submitted for
    327 // http://code.google.com/p/skia/issues/detail?id=1083.
    328 // SKP files might have invalid glyph ids. This test ensures they are ignored,
    329 // and there is no assert on input data in Debug mode.
    330 static void test_issue1083() {
    331     SkISize pageSize = SkISize::Make(100, 100);
    332     SkAutoTUnref<SkPDFDevice> dev(new SkPDFDevice(pageSize, pageSize, SkMatrix::I()));
    333 
    334     SkCanvas c(dev);
    335     SkPaint paint;
    336     paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
    337 
    338     uint16_t glyphID = 65000;
    339     c.drawText(&glyphID, 2, 0, 0, paint);
    340 
    341     SkPDFDocument doc;
    342     doc.appendPage(dev);
    343 
    344     SkDynamicMemoryWStream stream;
    345     doc.emitPDF(&stream);
    346 }
    347 
    348 DEF_TEST(PDFPrimitives, reporter) {
    349     SkAutoTUnref<SkPDFInt> int42(new SkPDFInt(42));
    350     SimpleCheckObjectOutput(reporter, int42.get(), "42");
    351 
    352     SkAutoTUnref<SkPDFScalar> realHalf(new SkPDFScalar(SK_ScalarHalf));
    353     SimpleCheckObjectOutput(reporter, realHalf.get(), "0.5");
    354 
    355     SkAutoTUnref<SkPDFScalar> bigScalar(new SkPDFScalar(110999.75f));
    356 #if !defined(SK_ALLOW_LARGE_PDF_SCALARS)
    357     SimpleCheckObjectOutput(reporter, bigScalar.get(), "111000");
    358 #else
    359     SimpleCheckObjectOutput(reporter, bigScalar.get(), "110999.75");
    360 
    361     SkAutoTUnref<SkPDFScalar> biggerScalar(new SkPDFScalar(50000000.1));
    362     SimpleCheckObjectOutput(reporter, biggerScalar.get(), "50000000");
    363 
    364     SkAutoTUnref<SkPDFScalar> smallestScalar(new SkPDFScalar(1.0/65536));
    365     SimpleCheckObjectOutput(reporter, smallestScalar.get(), "0.00001526");
    366 #endif
    367 
    368     SkAutoTUnref<SkPDFString> stringSimple(
    369         new SkPDFString("test ) string ( foo"));
    370     SimpleCheckObjectOutput(reporter, stringSimple.get(),
    371                             "(test \\) string \\( foo)");
    372     SkAutoTUnref<SkPDFString> stringComplex(
    373         new SkPDFString("\ttest ) string ( foo"));
    374     SimpleCheckObjectOutput(reporter, stringComplex.get(),
    375                             "<0974657374202920737472696E67202820666F6F>");
    376 
    377     SkAutoTUnref<SkPDFName> name(new SkPDFName("Test name\twith#tab"));
    378     const char expectedResult[] = "/Test#20name#09with#23tab";
    379     CheckObjectOutput(reporter, name.get(), expectedResult,
    380                       strlen(expectedResult), false, false);
    381 
    382     SkAutoTUnref<SkPDFName> escapedName(new SkPDFName("A#/%()<>[]{}B"));
    383     const char escapedNameExpected[] = "/A#23#2F#25#28#29#3C#3E#5B#5D#7B#7DB";
    384     CheckObjectOutput(reporter, escapedName.get(), escapedNameExpected,
    385                       strlen(escapedNameExpected), false, false);
    386 
    387     // Test that we correctly handle characters with the high-bit set.
    388     const unsigned char highBitCString[] = {0xDE, 0xAD, 'b', 'e', 0xEF, 0};
    389     SkAutoTUnref<SkPDFName> highBitName(
    390         new SkPDFName((const char*)highBitCString));
    391     const char highBitExpectedResult[] = "/#DE#ADbe#EF";
    392     CheckObjectOutput(reporter, highBitName.get(), highBitExpectedResult,
    393                       strlen(highBitExpectedResult), false, false);
    394 
    395     SkAutoTUnref<SkPDFArray> array(new SkPDFArray);
    396     SimpleCheckObjectOutput(reporter, array.get(), "[]");
    397     array->append(int42.get());
    398     SimpleCheckObjectOutput(reporter, array.get(), "[42]");
    399     array->append(realHalf.get());
    400     SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5]");
    401     SkAutoTUnref<SkPDFInt> int0(new SkPDFInt(0));
    402     array->append(int0.get());
    403     SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5 0]");
    404     SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
    405     array->setAt(0, int1.get());
    406     SimpleCheckObjectOutput(reporter, array.get(), "[1 0.5 0]");
    407 
    408     SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
    409     SimpleCheckObjectOutput(reporter, dict.get(), "<<>>");
    410     SkAutoTUnref<SkPDFName> n1(new SkPDFName("n1"));
    411     dict->insert(n1.get(), int42.get());
    412     SimpleCheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>");
    413     SkAutoTUnref<SkPDFName> n2(new SkPDFName("n2"));
    414     SkAutoTUnref<SkPDFName> n3(new SkPDFName("n3"));
    415     dict->insert(n2.get(), realHalf.get());
    416     dict->insert(n3.get(), array.get());
    417     SimpleCheckObjectOutput(reporter, dict.get(),
    418                             "<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>");
    419 
    420     TestPDFStream(reporter);
    421 
    422     TestCatalog(reporter);
    423 
    424     TestObjectRef(reporter);
    425 
    426     TestSubstitute(reporter);
    427 
    428     test_issue1083();
    429 
    430     TestImages(reporter);
    431 }
    432 
    433 namespace {
    434 
    435 class DummyImageFilter : public SkImageFilter {
    436 public:
    437     DummyImageFilter(bool visited = false) : SkImageFilter(0, NULL), fVisited(visited) {}
    438     virtual ~DummyImageFilter() SK_OVERRIDE {}
    439     virtual bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
    440                                SkBitmap* result, SkIPoint* offset) const {
    441         fVisited = true;
    442         offset->fX = offset->fY = 0;
    443         *result = src;
    444         return true;
    445     }
    446     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(DummyImageFilter)
    447 #ifdef SK_SUPPORT_LEGACY_DEEPFLATTENING
    448     explicit DummyImageFilter(SkReadBuffer& buffer) : SkImageFilter(0, NULL) {
    449         fVisited = buffer.readBool();
    450     }
    451 #endif
    452     bool visited() const { return fVisited; }
    453 
    454 private:
    455     mutable bool fVisited;
    456 };
    457 
    458 SkFlattenable* DummyImageFilter::CreateProc(SkReadBuffer& buffer) {
    459     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 0);
    460     bool visited = buffer.readBool();
    461     return SkNEW_ARGS(DummyImageFilter, (visited));
    462 }
    463 
    464 };
    465 
    466 // Check that PDF rendering of image filters successfully falls back to
    467 // CPU rasterization.
    468 DEF_TEST(PDFImageFilter, reporter) {
    469     SkISize pageSize = SkISize::Make(100, 100);
    470     SkAutoTUnref<SkPDFDevice> device(new SkPDFDevice(pageSize, pageSize, SkMatrix::I()));
    471     SkCanvas canvas(device.get());
    472     SkAutoTUnref<DummyImageFilter> filter(new DummyImageFilter());
    473 
    474     // Filter just created; should be unvisited.
    475     REPORTER_ASSERT(reporter, !filter->visited());
    476     SkPaint paint;
    477     paint.setImageFilter(filter.get());
    478     canvas.drawRect(SkRect::MakeWH(100, 100), paint);
    479 
    480     // Filter was used in rendering; should be visited.
    481     REPORTER_ASSERT(reporter, filter->visited());
    482 }
    483