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
10#include <string>
11
12#include "Test.h"
13#include "SkData.h"
14#include "SkFlate.h"
15#include "SkPDFCatalog.h"
16#include "SkPDFStream.h"
17#include "SkPDFTypes.h"
18#include "SkScalar.h"
19#include "SkStream.h"
20#include "SkTypes.h"
21
22class SkPDFTestDict : public SkPDFDict {
23public:
24    void getResources(SkTDArray<SkPDFObject*>* resourceList) {
25        resourceList->setReserve(resourceList->count() + fResources.count());
26        for (int i = 0; i < fResources.count(); i++) {
27            resourceList->push(fResources[i]);
28            fResources[i]->ref();
29        }
30    }
31
32    void addResource(SkPDFObject* object) {
33        fResources.append(1, &object);
34    }
35
36private:
37    SkTDArray<SkPDFObject*> fResources;
38};
39
40static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
41                          const void* buffer, size_t len) {
42    SkAutoDataUnref data(stream.copyToData());
43    if (offset + len > data.size()) {
44        return false;
45    }
46    return memcmp(data.bytes() + offset, buffer, len) == 0;
47}
48
49static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
50                              const char* expectedData, size_t expectedSize,
51                              bool indirect, bool compression) {
52    SkPDFDocument::Flags docFlags = (SkPDFDocument::Flags) 0;
53    if (!compression) {
54        docFlags = SkTBitOr(docFlags, SkPDFDocument::kNoCompression_Flag);
55    }
56    SkPDFCatalog catalog(docFlags);
57    size_t directSize = obj->getOutputSize(&catalog, false);
58    REPORTER_ASSERT(reporter, directSize == expectedSize);
59
60    SkDynamicMemoryWStream buffer;
61    obj->emit(&buffer, &catalog, false);
62    REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
63    REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedData,
64                                            directSize));
65
66    if (indirect) {
67        // Indirect output.
68        static char header[] = "1 0 obj\n";
69        static size_t headerLen = strlen(header);
70        static char footer[] = "\nendobj\n";
71        static size_t footerLen = strlen(footer);
72
73        catalog.addObject(obj, false);
74
75        size_t indirectSize = obj->getOutputSize(&catalog, true);
76        REPORTER_ASSERT(reporter,
77                        indirectSize == directSize + headerLen + footerLen);
78
79        buffer.reset();
80        obj->emit(&buffer, &catalog, true);
81        REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset());
82        REPORTER_ASSERT(reporter, stream_equals(buffer, 0, header, headerLen));
83        REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen, expectedData,
84                                                directSize));
85        REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen + directSize,
86                                                footer, footerLen));
87    }
88}
89
90static void SimpleCheckObjectOutput(skiatest::Reporter* reporter,
91                                    SkPDFObject* obj,
92                                    const std::string& expectedResult) {
93    CheckObjectOutput(reporter, obj, expectedResult.c_str(),
94                      expectedResult.length(), true, false);
95}
96
97static void TestPDFStream(skiatest::Reporter* reporter) {
98    char streamBytes[] = "Test\nFoo\tBar";
99    SkRefPtr<SkMemoryStream> streamData = new SkMemoryStream(
100        streamBytes, strlen(streamBytes), true);
101    streamData->unref();  // SkRefPtr and new both took a reference.
102    SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData.get());
103    stream->unref();  // SkRefPtr and new both took a reference.
104    SimpleCheckObjectOutput(
105        reporter, stream.get(),
106        "<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream");
107    stream->insert("Attribute", new SkPDFInt(42))->unref();
108    SimpleCheckObjectOutput(reporter, stream.get(),
109                            "<</Length 12\n/Attribute 42\n>> stream\n"
110                                "Test\nFoo\tBar\nendstream");
111
112    if (SkFlate::HaveFlate()) {
113        char streamBytes2[] = "This is a longer string, so that compression "
114                              "can do something with it. With shorter strings, "
115                              "the short circuit logic cuts in and we end up "
116                              "with an uncompressed string.";
117        SkAutoDataUnref streamData2(SkData::NewWithCopy(streamBytes2,
118                                                        strlen(streamBytes2)));
119        SkRefPtr<SkPDFStream> stream = new SkPDFStream(streamData2.get());
120        stream->unref();  // SkRefPtr and new both took a reference.
121
122        SkDynamicMemoryWStream compressedByteStream;
123        SkFlate::Deflate(streamData2.get(), &compressedByteStream);
124        SkAutoDataUnref compressedData(compressedByteStream.copyToData());
125
126        // Check first without compression.
127        SkDynamicMemoryWStream expectedResult1;
128        expectedResult1.writeText("<</Length 167\n>> stream\n");
129        expectedResult1.writeText(streamBytes2);
130        expectedResult1.writeText("\nendstream");
131        SkAutoDataUnref expectedResultData1(expectedResult1.copyToData());
132        CheckObjectOutput(reporter, stream.get(),
133                          (const char*) expectedResultData1.data(),
134                          expectedResultData1.size(), true, false);
135
136        // Then again with compression.
137        SkDynamicMemoryWStream expectedResult2;
138        expectedResult2.writeText("<</Filter /FlateDecode\n/Length 116\n"
139                                 ">> stream\n");
140        expectedResult2.write(compressedData.data(), compressedData.size());
141        expectedResult2.writeText("\nendstream");
142        SkAutoDataUnref expectedResultData2(expectedResult2.copyToData());
143        CheckObjectOutput(reporter, stream.get(),
144                          (const char*) expectedResultData2.data(),
145                          expectedResultData2.size(), true, true);
146    }
147}
148
149static void TestCatalog(skiatest::Reporter* reporter) {
150    SkPDFCatalog catalog((SkPDFDocument::Flags)0);
151    SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
152    int1->unref();  // SkRefPtr and new both took a reference.
153    SkRefPtr<SkPDFInt> int2 = new SkPDFInt(2);
154    int2->unref();  // SkRefPtr and new both took a reference.
155    SkRefPtr<SkPDFInt> int3 = new SkPDFInt(3);
156    int3->unref();  // SkRefPtr and new both took a reference.
157    SkRefPtr<SkPDFInt> int1Again(int1.get());
158
159    catalog.addObject(int1.get(), false);
160    catalog.addObject(int2.get(), false);
161    catalog.addObject(int3.get(), false);
162
163    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
164    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
165    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int3.get()) == 3);
166
167    SkDynamicMemoryWStream buffer;
168    catalog.emitObjectNumber(&buffer, int1.get());
169    catalog.emitObjectNumber(&buffer, int2.get());
170    catalog.emitObjectNumber(&buffer, int3.get());
171    catalog.emitObjectNumber(&buffer, int1Again.get());
172    char expectedResult[] = "1 02 03 01 0";
173    REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
174                                            strlen(expectedResult)));
175}
176
177static void TestObjectRef(skiatest::Reporter* reporter) {
178    SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
179    int1->unref();  // SkRefPtr and new both took a reference.
180    SkRefPtr<SkPDFInt> int2 = new SkPDFInt(2);
181    int2->unref();  // SkRefPtr and new both took a reference.
182    SkRefPtr<SkPDFObjRef> int2ref = new SkPDFObjRef(int2.get());
183    int2ref->unref();  // SkRefPtr and new both took a reference.
184
185    SkPDFCatalog catalog((SkPDFDocument::Flags)0);
186    catalog.addObject(int1.get(), false);
187    catalog.addObject(int2.get(), false);
188    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int1.get()) == 3);
189    REPORTER_ASSERT(reporter, catalog.getObjectNumberSize(int2.get()) == 3);
190
191    char expectedResult[] = "2 0 R";
192    SkDynamicMemoryWStream buffer;
193    int2ref->emitObject(&buffer, &catalog, false);
194    REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
195    REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
196                                            buffer.getOffset()));
197}
198
199static void TestSubstitute(skiatest::Reporter* reporter) {
200    SkRefPtr<SkPDFTestDict> proxy = new SkPDFTestDict();
201    proxy->unref();  // SkRefPtr and new both took a reference.
202    SkRefPtr<SkPDFTestDict> stub = new SkPDFTestDict();
203    stub->unref();  // SkRefPtr and new both took a reference.
204    SkRefPtr<SkPDFInt> int33 = new SkPDFInt(33);
205    int33->unref();  // SkRefPtr and new both took a reference.
206    SkRefPtr<SkPDFDict> stubResource = new SkPDFDict();
207    stubResource->unref();  // SkRefPtr and new both took a reference.
208    SkRefPtr<SkPDFInt> int44 = new SkPDFInt(44);
209    int44->unref();  // SkRefPtr and new both took a reference.
210
211    stub->insert("Value", int33.get());
212    stubResource->insert("InnerValue", int44.get());
213    stub->addResource(stubResource.get());
214
215    SkPDFCatalog catalog((SkPDFDocument::Flags)0);
216    catalog.addObject(proxy.get(), false);
217    catalog.setSubstitute(proxy.get(), stub.get());
218
219    SkDynamicMemoryWStream buffer;
220    proxy->emit(&buffer, &catalog, false);
221    catalog.emitSubstituteResources(&buffer, false);
222
223    char objectResult[] = "2 0 obj\n<</Value 33\n>>\nendobj\n";
224    REPORTER_ASSERT(
225        reporter,
226        catalog.setFileOffset(proxy.get(), 0) == strlen(objectResult));
227
228    char expectedResult[] =
229        "<</Value 33\n>>1 0 obj\n<</InnerValue 44\n>>\nendobj\n";
230    REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
231    REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
232                                            buffer.getOffset()));
233}
234
235static void TestPDFPrimitives(skiatest::Reporter* reporter) {
236    SkRefPtr<SkPDFInt> int42 = new SkPDFInt(42);
237    int42->unref();  // SkRefPtr and new both took a reference.
238    SimpleCheckObjectOutput(reporter, int42.get(), "42");
239
240    SkRefPtr<SkPDFScalar> realHalf = new SkPDFScalar(SK_ScalarHalf);
241    realHalf->unref();  // SkRefPtr and new both took a reference.
242    SimpleCheckObjectOutput(reporter, realHalf.get(), "0.5");
243
244#if defined(SK_SCALAR_IS_FLOAT)
245    SkRefPtr<SkPDFScalar> bigScalar = new SkPDFScalar(110999.75);
246    bigScalar->unref();  // SkRefPtr and new both took a reference.
247#if !defined(SK_ALLOW_LARGE_PDF_SCALARS)
248    SimpleCheckObjectOutput(reporter, bigScalar.get(), "111000");
249#else
250    SimpleCheckObjectOutput(reporter, bigScalar.get(), "110999.75");
251
252    SkRefPtr<SkPDFScalar> biggerScalar = new SkPDFScalar(50000000.1);
253    biggerScalar->unref();  // SkRefPtr and new both took a reference.
254    SimpleCheckObjectOutput(reporter, biggerScalar.get(), "50000000");
255
256    SkRefPtr<SkPDFScalar> smallestScalar = new SkPDFScalar(1.0/65536);
257    smallestScalar->unref();  // SkRefPtr and new both took a reference.
258    SimpleCheckObjectOutput(reporter, smallestScalar.get(), "0.00001526");
259#endif
260#endif
261
262    SkRefPtr<SkPDFString> stringSimple = new SkPDFString("test ) string ( foo");
263    stringSimple->unref();  // SkRefPtr and new both took a reference.
264    SimpleCheckObjectOutput(reporter, stringSimple.get(),
265                            "(test \\) string \\( foo)");
266    SkRefPtr<SkPDFString> stringComplex =
267        new SkPDFString("\ttest ) string ( foo");
268    stringComplex->unref();  // SkRefPtr and new both took a reference.
269    SimpleCheckObjectOutput(reporter, stringComplex.get(),
270                            "<0974657374202920737472696E67202820666F6F>");
271
272    SkRefPtr<SkPDFName> name = new SkPDFName("Test name\twith#tab");
273    name->unref();  // SkRefPtr and new both took a reference.
274    const char expectedResult[] = "/Test#20name#09with#23tab";
275    CheckObjectOutput(reporter, name.get(), expectedResult,
276                      strlen(expectedResult), false, false);
277
278    SkRefPtr<SkPDFArray> array = new SkPDFArray;
279    array->unref();  // SkRefPtr and new both took a reference.
280    SimpleCheckObjectOutput(reporter, array.get(), "[]");
281    array->append(int42.get());
282    SimpleCheckObjectOutput(reporter, array.get(), "[42]");
283    array->append(realHalf.get());
284    SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5]");
285    SkRefPtr<SkPDFInt> int0 = new SkPDFInt(0);
286    int0->unref();  // SkRefPtr and new both took a reference.
287    array->append(int0.get());
288    SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5 0]");
289    SkRefPtr<SkPDFInt> int1 = new SkPDFInt(1);
290    int1->unref();  // SkRefPtr and new both took a reference.
291    array->setAt(0, int1.get());
292    SimpleCheckObjectOutput(reporter, array.get(), "[1 0.5 0]");
293
294    SkRefPtr<SkPDFDict> dict = new SkPDFDict;
295    dict->unref();  // SkRefPtr and new both took a reference.
296    SimpleCheckObjectOutput(reporter, dict.get(), "<<>>");
297    SkRefPtr<SkPDFName> n1 = new SkPDFName("n1");
298    n1->unref();  // SkRefPtr and new both took a reference.
299    dict->insert(n1.get(), int42.get());
300    SimpleCheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>");
301    SkRefPtr<SkPDFName> n2 = new SkPDFName("n2");
302    n2->unref();  // SkRefPtr and new both took a reference.
303    SkRefPtr<SkPDFName> n3 = new SkPDFName("n3");
304    n3->unref();  // SkRefPtr and new both took a reference.
305    dict->insert(n2.get(), realHalf.get());
306    dict->insert(n3.get(), array.get());
307    SimpleCheckObjectOutput(reporter, dict.get(),
308                            "<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>");
309
310    TestPDFStream(reporter);
311
312    TestCatalog(reporter);
313
314    TestObjectRef(reporter);
315
316    TestSubstitute(reporter);
317}
318
319#include "TestClassDef.h"
320DEFINE_TESTCLASS("PDFPrimitives", PDFPrimitivesTestClass, TestPDFPrimitives)
321