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