Home | History | Annotate | Download | only in tests
      1 
      2 /*
      3  * Copyright 2010 The Android Open Source Project
      4  *
      5  * Use of this source code is governed by a BSD-style license that can be
      6  * found in the LICENSE file.
      7  */
      8 
      9 #include "Test.h"
     10 #include "SkBitmap.h"
     11 #include "SkCanvas.h"
     12 #include "SkData.h"
     13 #include "SkFlate.h"
     14 #include "SkImageEncoder.h"
     15 #include "SkMatrix.h"
     16 #include "SkPDFCatalog.h"
     17 #include "SkPDFDevice.h"
     18 #include "SkPDFStream.h"
     19 #include "SkPDFTypes.h"
     20 #include "SkScalar.h"
     21 #include "SkStream.h"
     22 #include "SkTypes.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 static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect) {
     43     stream->writeText("DCT compessed stream.");
     44     return true;
     45 }
     46 
     47 static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
     48                           const void* buffer, size_t len) {
     49     SkAutoDataUnref data(stream.copyToData());
     50     if (offset + len > data->size()) {
     51         return false;
     52     }
     53     return memcmp(data->bytes() + offset, buffer, len) == 0;
     54 }
     55 
     56 static bool stream_contains(const SkDynamicMemoryWStream& stream,
     57                             const char* buffer) {
     58     SkAutoDataUnref data(stream.copyToData());
     59     int len = strlen(buffer);  // our buffer does not have EOSs.
     60 
     61     for (int offset = 0 ; offset < (int)data->size() - len; offset++) {
     62         if (memcmp(data->bytes() + offset, buffer, len) == 0) {
     63             return true;
     64         }
     65     }
     66 
     67     return false;
     68 }
     69 
     70 static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
     71                               const char* expectedData, size_t expectedSize,
     72                               bool indirect, bool compression) {
     73     SkPDFDocument::Flags docFlags = (SkPDFDocument::Flags) 0;
     74     if (!compression) {
     75         docFlags = SkTBitOr(docFlags, SkPDFDocument::kFavorSpeedOverSize_Flags);
     76     }
     77     SkPDFCatalog catalog(docFlags);
     78     size_t directSize = obj->getOutputSize(&catalog, false);
     79     REPORTER_ASSERT(reporter, directSize == expectedSize);
     80 
     81     SkDynamicMemoryWStream buffer;
     82     obj->emit(&buffer, &catalog, false);
     83     REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
     84     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedData,
     85                                             directSize));
     86 
     87     if (indirect) {
     88         // Indirect output.
     89         static char header[] = "1 0 obj\n";
     90         static size_t headerLen = strlen(header);
     91         static char footer[] = "\nendobj\n";
     92         static size_t footerLen = strlen(footer);
     93 
     94         catalog.addObject(obj, false);
     95 
     96         size_t indirectSize = obj->getOutputSize(&catalog, true);
     97         REPORTER_ASSERT(reporter,
     98                         indirectSize == directSize + headerLen + footerLen);
     99 
    100         buffer.reset();
    101         obj->emit(&buffer, &catalog, true);
    102         REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset());
    103         REPORTER_ASSERT(reporter, stream_equals(buffer, 0, header, headerLen));
    104         REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen, expectedData,
    105                                                 directSize));
    106         REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen + directSize,
    107                                                 footer, footerLen));
    108     }
    109 }
    110 
    111 static void SimpleCheckObjectOutput(skiatest::Reporter* reporter,
    112                                     SkPDFObject* obj,
    113                                     const char* expectedResult) {
    114     CheckObjectOutput(reporter, obj, expectedResult,
    115                       strlen(expectedResult), true, false);
    116 }
    117 
    118 static void TestPDFStream(skiatest::Reporter* reporter) {
    119     char streamBytes[] = "Test\nFoo\tBar";
    120     SkAutoTUnref<SkMemoryStream> streamData(new SkMemoryStream(
    121         streamBytes, strlen(streamBytes), true));
    122     SkAutoTUnref<SkPDFStream> stream(new SkPDFStream(streamData.get()));
    123     SimpleCheckObjectOutput(
    124         reporter, stream.get(),
    125         "<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream");
    126     stream->insert("Attribute", new SkPDFInt(42))->unref();
    127     SimpleCheckObjectOutput(reporter, stream.get(),
    128                             "<</Length 12\n/Attribute 42\n>> stream\n"
    129                                 "Test\nFoo\tBar\nendstream");
    130 
    131     if (SkFlate::HaveFlate()) {
    132         char streamBytes2[] = "This is a longer string, so that compression "
    133                               "can do something with it. With shorter strings, "
    134                               "the short circuit logic cuts in and we end up "
    135                               "with an uncompressed string.";
    136         SkAutoDataUnref streamData2(SkData::NewWithCopy(streamBytes2,
    137                                                         strlen(streamBytes2)));
    138         SkAutoTUnref<SkPDFStream> stream(new SkPDFStream(streamData2.get()));
    139 
    140         SkDynamicMemoryWStream compressedByteStream;
    141         SkFlate::Deflate(streamData2.get(), &compressedByteStream);
    142         SkAutoDataUnref compressedData(compressedByteStream.copyToData());
    143 
    144         // Check first without compression.
    145         SkDynamicMemoryWStream expectedResult1;
    146         expectedResult1.writeText("<</Length 167\n>> stream\n");
    147         expectedResult1.writeText(streamBytes2);
    148         expectedResult1.writeText("\nendstream");
    149         SkAutoDataUnref expectedResultData1(expectedResult1.copyToData());
    150         CheckObjectOutput(reporter, stream.get(),
    151                           (const char*) expectedResultData1->data(),
    152                           expectedResultData1->size(), true, false);
    153 
    154         // Then again with compression.
    155         SkDynamicMemoryWStream expectedResult2;
    156         expectedResult2.writeText("<</Filter /FlateDecode\n/Length 116\n"
    157                                  ">> stream\n");
    158         expectedResult2.write(compressedData->data(), compressedData->size());
    159         expectedResult2.writeText("\nendstream");
    160         SkAutoDataUnref expectedResultData2(expectedResult2.copyToData());
    161         CheckObjectOutput(reporter, stream.get(),
    162                           (const char*) expectedResultData2->data(),
    163                           expectedResultData2->size(), true, true);
    164     }
    165 }
    166 
    167 static void TestCatalog(skiatest::Reporter* reporter) {
    168     SkPDFCatalog catalog((SkPDFDocument::Flags)0);
    169     SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
    170     SkAutoTUnref<SkPDFInt> int2(new SkPDFInt(2));
    171     SkAutoTUnref<SkPDFInt> int3(new SkPDFInt(3));
    172     int1.get()->ref();
    173     SkAutoTUnref<SkPDFInt> int1Again(int1.get());
    174 
    175     catalog.addObject(int1.get(), false);
    176     catalog.addObject(int2.get(), false);
    177     catalog.addObject(int3.get(), false);
    178 
    179     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
    180     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
    181     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int3.get()) == 3);
    182 
    183     SkDynamicMemoryWStream buffer;
    184     catalog.emitObjectNumber(&buffer, int1.get());
    185     catalog.emitObjectNumber(&buffer, int2.get());
    186     catalog.emitObjectNumber(&buffer, int3.get());
    187     catalog.emitObjectNumber(&buffer, int1Again.get());
    188     char expectedResult[] = "1 02 03 01 0";
    189     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
    190                                             strlen(expectedResult)));
    191 }
    192 
    193 static void TestObjectRef(skiatest::Reporter* reporter) {
    194     SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
    195     SkAutoTUnref<SkPDFInt> int2(new SkPDFInt(2));
    196     SkAutoTUnref<SkPDFObjRef> int2ref(new SkPDFObjRef(int2.get()));
    197 
    198     SkPDFCatalog catalog((SkPDFDocument::Flags)0);
    199     catalog.addObject(int1.get(), false);
    200     catalog.addObject(int2.get(), false);
    201     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
    202     REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
    203 
    204     char expectedResult[] = "2 0 R";
    205     SkDynamicMemoryWStream buffer;
    206     int2ref->emitObject(&buffer, &catalog, false);
    207     REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
    208     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
    209                                             buffer.getOffset()));
    210 }
    211 
    212 static void TestSubstitute(skiatest::Reporter* reporter) {
    213     SkAutoTUnref<SkPDFTestDict> proxy(new SkPDFTestDict());
    214     SkAutoTUnref<SkPDFTestDict> stub(new SkPDFTestDict());
    215     SkAutoTUnref<SkPDFInt> int33(new SkPDFInt(33));
    216     SkAutoTUnref<SkPDFDict> stubResource(new SkPDFDict());
    217     SkAutoTUnref<SkPDFInt> int44(new SkPDFInt(44));
    218 
    219     stub->insert("Value", int33.get());
    220     stubResource->insert("InnerValue", int44.get());
    221     stub->addResource(stubResource.get());
    222 
    223     SkPDFCatalog catalog((SkPDFDocument::Flags)0);
    224     catalog.addObject(proxy.get(), false);
    225     catalog.setSubstitute(proxy.get(), stub.get());
    226 
    227     SkDynamicMemoryWStream buffer;
    228     proxy->emit(&buffer, &catalog, false);
    229     catalog.emitSubstituteResources(&buffer, false);
    230 
    231     char objectResult[] = "2 0 obj\n<</Value 33\n>>\nendobj\n";
    232     REPORTER_ASSERT(
    233         reporter,
    234         catalog.setFileOffset(proxy.get(), 0) == strlen(objectResult));
    235 
    236     char expectedResult[] =
    237         "<</Value 33\n>>1 0 obj\n<</InnerValue 44\n>>\nendobj\n";
    238     REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
    239     REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
    240                                             buffer.getOffset()));
    241 }
    242 
    243 // Create a bitmap that would be very eficiently compressed in a ZIP.
    244 static void setup_bitmap(SkBitmap* bitmap, int width, int height) {
    245     bitmap->setConfig(SkBitmap::kARGB_8888_Config, width, height);
    246     bitmap->allocPixels();
    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     SkPDFDevice* dev = new SkPDFDevice(pageSize, pageSize, SkMatrix::I());
    254 
    255     if (useDCTEncoder) {
    256         dev->setDCTEncoder(encode_to_dct_stream);
    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 static void TestPDFPrimitives(skiatest::Reporter* 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 #if defined(SK_SCALAR_IS_FLOAT)
    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 #endif
    368 
    369     SkAutoTUnref<SkPDFString> stringSimple(
    370         new SkPDFString("test ) string ( foo"));
    371     SimpleCheckObjectOutput(reporter, stringSimple.get(),
    372                             "(test \\) string \\( foo)");
    373     SkAutoTUnref<SkPDFString> stringComplex(
    374         new SkPDFString("\ttest ) string ( foo"));
    375     SimpleCheckObjectOutput(reporter, stringComplex.get(),
    376                             "<0974657374202920737472696E67202820666F6F>");
    377 
    378     SkAutoTUnref<SkPDFName> name(new SkPDFName("Test name\twith#tab"));
    379     const char expectedResult[] = "/Test#20name#09with#23tab";
    380     CheckObjectOutput(reporter, name.get(), expectedResult,
    381                       strlen(expectedResult), false, false);
    382 
    383     SkAutoTUnref<SkPDFName> escapedName(new SkPDFName("A#/%()<>[]{}B"));
    384     const char escapedNameExpected[] = "/A#23#2F#25#28#29#3C#3E#5B#5D#7B#7DB";
    385     CheckObjectOutput(reporter, escapedName.get(), escapedNameExpected,
    386                       strlen(escapedNameExpected), false, false);
    387 
    388     // Test that we correctly handle characters with the high-bit set.
    389     const unsigned char highBitCString[] = {0xDE, 0xAD, 'b', 'e', 0xEF, 0};
    390     SkAutoTUnref<SkPDFName> highBitName(
    391         new SkPDFName((const char*)highBitCString));
    392     const char highBitExpectedResult[] = "/#DE#ADbe#EF";
    393     CheckObjectOutput(reporter, highBitName.get(), highBitExpectedResult,
    394                       strlen(highBitExpectedResult), false, false);
    395 
    396     SkAutoTUnref<SkPDFArray> array(new SkPDFArray);
    397     SimpleCheckObjectOutput(reporter, array.get(), "[]");
    398     array->append(int42.get());
    399     SimpleCheckObjectOutput(reporter, array.get(), "[42]");
    400     array->append(realHalf.get());
    401     SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5]");
    402     SkAutoTUnref<SkPDFInt> int0(new SkPDFInt(0));
    403     array->append(int0.get());
    404     SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5 0]");
    405     SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
    406     array->setAt(0, int1.get());
    407     SimpleCheckObjectOutput(reporter, array.get(), "[1 0.5 0]");
    408 
    409     SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
    410     SimpleCheckObjectOutput(reporter, dict.get(), "<<>>");
    411     SkAutoTUnref<SkPDFName> n1(new SkPDFName("n1"));
    412     dict->insert(n1.get(), int42.get());
    413     SimpleCheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>");
    414     SkAutoTUnref<SkPDFName> n2(new SkPDFName("n2"));
    415     SkAutoTUnref<SkPDFName> n3(new SkPDFName("n3"));
    416     dict->insert(n2.get(), realHalf.get());
    417     dict->insert(n3.get(), array.get());
    418     SimpleCheckObjectOutput(reporter, dict.get(),
    419                             "<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>");
    420 
    421     TestPDFStream(reporter);
    422 
    423     TestCatalog(reporter);
    424 
    425     TestObjectRef(reporter);
    426 
    427     TestSubstitute(reporter);
    428 
    429     test_issue1083();
    430 
    431     TestImages(reporter);
    432 }
    433 
    434 #include "TestClassDef.h"
    435 DEFINE_TESTCLASS("PDFPrimitives", PDFPrimitivesTestClass, TestPDFPrimitives)
    436