2 * Copyright 2010 The Android Open Source Project
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
11 #include "SkDocument.h"
13 #include "SkImageEncoder.h"
15 #include "SkPDFCanon.h"
16 #include "SkPDFCatalog.h"
17 #include "SkPDFDevice.h"
18 #include "SkPDFStream.h"
19 #include "SkPDFTypes.h"
20 #include "SkReadBuffer.h"
26 #define DUMMY_TEXT "DCT compessed stream."
28 static bool stream_equals(const SkDynamicMemoryWStream& stream, size_t offset,
29 const void* buffer, size_t len) {
30 SkAutoDataUnref data(stream.copyToData());
31 if (offset + len > data->size()) {
34 return memcmp(data->bytes() + offset, buffer, len) == 0;
37 static void emit_object(SkPDFObject* object,
39 SkPDFCatalog* catalog,
41 SkPDFObject* realObject = catalog->getSubstituteObject(object);
43 stream->writeDecAsText(catalog->getObjectNumber(object));
44 stream->writeText(" 0 obj\n"); // Generation number is always 0.
45 realObject->emitObject(stream, catalog);
46 stream->writeText("\nendobj\n");
48 realObject->emitObject(stream, catalog);
52 static size_t get_output_size(SkPDFObject* object,
53 SkPDFCatalog* catalog,
55 SkDynamicMemoryWStream buffer;
56 emit_object(object, &buffer, catalog, indirect);
57 return buffer.getOffset();
60 static void CheckObjectOutput(skiatest::Reporter* reporter, SkPDFObject* obj,
61 const char* expectedData, size_t expectedSize,
64 size_t directSize = get_output_size(obj, &catalog, false);
65 REPORTER_ASSERT(reporter, directSize == expectedSize);
67 SkDynamicMemoryWStream buffer;
68 emit_object(obj, &buffer, &catalog, false);
69 REPORTER_ASSERT(reporter, directSize == buffer.getOffset());
70 REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedData,
75 static char header[] = "1 0 obj\n";
76 static size_t headerLen = strlen(header);
77 static char footer[] = "\nendobj\n";
78 static size_t footerLen = strlen(footer);
80 catalog.addObject(obj);
82 size_t indirectSize = get_output_size(obj, &catalog, true);
83 REPORTER_ASSERT(reporter,
84 indirectSize == directSize + headerLen + footerLen);
87 emit_object(obj, &buffer, &catalog, true);
88 REPORTER_ASSERT(reporter, indirectSize == buffer.getOffset());
89 REPORTER_ASSERT(reporter, stream_equals(buffer, 0, header, headerLen));
90 REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen, expectedData,
92 REPORTER_ASSERT(reporter, stream_equals(buffer, headerLen + directSize,
97 static void SimpleCheckObjectOutput(skiatest::Reporter* reporter,
99 const char* expectedResult) {
100 CheckObjectOutput(reporter, obj, expectedResult,
101 strlen(expectedResult), true);
104 static void TestPDFStream(skiatest::Reporter* reporter) {
105 char streamBytes[] = "Test\nFoo\tBar";
106 SkAutoTDelete<SkMemoryStream> streamData(new SkMemoryStream(
107 streamBytes, strlen(streamBytes), true));
108 SkAutoTUnref<SkPDFStream> stream(new SkPDFStream(streamData.get()));
109 SimpleCheckObjectOutput(
110 reporter, stream.get(),
111 "<</Length 12\n>> stream\nTest\nFoo\tBar\nendstream");
112 stream->insert("Attribute", new SkPDFInt(42))->unref();
113 SimpleCheckObjectOutput(reporter, stream.get(),
114 "<</Length 12\n/Attribute 42\n>> stream\n"
115 "Test\nFoo\tBar\nendstream");
119 char streamBytes2[] = "This is a longer string, so that compression "
120 "can do something with it. With shorter strings, "
121 "the short circuit logic cuts in and we end up "
122 "with an uncompressed string.";
123 SkAutoDataUnref streamData2(SkData::NewWithCopy(streamBytes2,
124 strlen(streamBytes2)));
125 SkAutoTUnref<SkPDFStream> stream(new SkPDFStream(streamData2.get()));
127 SkDynamicMemoryWStream compressedByteStream;
128 SkFlate::Deflate(streamData2.get(), &compressedByteStream);
129 SkAutoDataUnref compressedData(compressedByteStream.copyToData());
131 SkDynamicMemoryWStream expected;
132 expected.writeText("<</Filter /FlateDecode\n/Length 116\n"
134 expected.write(compressedData->data(), compressedData->size());
135 expected.writeText("\nendstream");
136 SkAutoDataUnref expectedResultData2(expected.copyToData());
137 CheckObjectOutput(reporter, stream.get(),
138 (const char*) expectedResultData2->data(),
139 expectedResultData2->size(), true);
141 #endif // SK_NO_FLATE
144 static void TestCatalog(skiatest::Reporter* reporter) {
145 SkPDFCatalog catalog;
146 SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
147 SkAutoTUnref<SkPDFInt> int2(new SkPDFInt(2));
148 SkAutoTUnref<SkPDFInt> int3(new SkPDFInt(3));
150 SkAutoTUnref<SkPDFInt> int1Again(int1.get());
152 catalog.addObject(int1.get());
153 catalog.addObject(int2.get());
154 catalog.addObject(int3.get());
156 REPORTER_ASSERT(reporter, catalog.getObjectNumber(int1.get()) == 1);
157 REPORTER_ASSERT(reporter, catalog.getObjectNumber(int2.get()) == 2);
158 REPORTER_ASSERT(reporter, catalog.getObjectNumber(int3.get()) == 3);
159 REPORTER_ASSERT(reporter, catalog.getObjectNumber(int1Again.get()) == 1);
162 static void TestObjectRef(skiatest::Reporter* reporter) {
163 SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
164 SkAutoTUnref<SkPDFInt> int2(new SkPDFInt(2));
165 SkAutoTUnref<SkPDFObjRef> int2ref(new SkPDFObjRef(int2.get()));
167 SkPDFCatalog catalog;
168 catalog.addObject(int1.get());
169 catalog.addObject(int2.get());
170 REPORTER_ASSERT(reporter, catalog.getObjectNumber(int1.get()) == 1);
171 REPORTER_ASSERT(reporter, catalog.getObjectNumber(int2.get()) == 2);
173 char expectedResult[] = "2 0 R";
174 SkDynamicMemoryWStream buffer;
175 int2ref->emitObject(&buffer, &catalog);
176 REPORTER_ASSERT(reporter, buffer.getOffset() == strlen(expectedResult));
177 REPORTER_ASSERT(reporter, stream_equals(buffer, 0, expectedResult,
178 buffer.getOffset()));
181 static void TestSubstitute(skiatest::Reporter* reporter) {
182 SkAutoTUnref<SkPDFDict> proxy(new SkPDFDict());
183 SkAutoTUnref<SkPDFDict> stub(new SkPDFDict());
185 proxy->insert("Value", new SkPDFInt(33))->unref();
186 stub->insert("Value", new SkPDFInt(44))->unref();
188 SkPDFCatalog catalog;
189 catalog.addObject(proxy.get());
190 catalog.setSubstitute(proxy.get(), stub.get());
192 REPORTER_ASSERT(reporter, stub.get() == catalog.getSubstituteObject(proxy));
193 REPORTER_ASSERT(reporter, proxy.get() != catalog.getSubstituteObject(stub));
196 // This test used to assert without the fix submitted for
197 // http://code.google.com/p/skia/issues/detail?id=1083.
198 // SKP files might have invalid glyph ids. This test ensures they are ignored,
199 // and there is no assert on input data in Debug mode.
200 static void test_issue1083() {
201 SkDynamicMemoryWStream outStream;
202 SkAutoTUnref<SkDocument> doc(SkDocument::CreatePDF(&outStream));
203 SkCanvas* canvas = doc->beginPage(100.0f, 100.0f);
205 paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
207 uint16_t glyphID = 65000;
208 canvas->drawText(&glyphID, 2, 0, 0, paint);
213 DEF_TEST(PDFPrimitives, reporter) {
214 SkAutoTUnref<SkPDFInt> int42(new SkPDFInt(42));
215 SimpleCheckObjectOutput(reporter, int42.get(), "42");
217 SkAutoTUnref<SkPDFScalar> realHalf(new SkPDFScalar(SK_ScalarHalf));
218 SimpleCheckObjectOutput(reporter, realHalf.get(), "0.5");
220 SkAutoTUnref<SkPDFScalar> bigScalar(new SkPDFScalar(110999.75f));
221 #if !defined(SK_ALLOW_LARGE_PDF_SCALARS)
222 SimpleCheckObjectOutput(reporter, bigScalar.get(), "111000");
224 SimpleCheckObjectOutput(reporter, bigScalar.get(), "110999.75");
226 SkAutoTUnref<SkPDFScalar> biggerScalar(new SkPDFScalar(50000000.1));
227 SimpleCheckObjectOutput(reporter, biggerScalar.get(), "50000000");
229 SkAutoTUnref<SkPDFScalar> smallestScalar(new SkPDFScalar(1.0/65536));
230 SimpleCheckObjectOutput(reporter, smallestScalar.get(), "0.00001526");
233 SkAutoTUnref<SkPDFString> stringSimple(
234 new SkPDFString("test ) string ( foo"));
235 SimpleCheckObjectOutput(reporter, stringSimple.get(),
236 "(test \\) string \\( foo)");
237 SkAutoTUnref<SkPDFString> stringComplex(
238 new SkPDFString("\ttest ) string ( foo"));
239 SimpleCheckObjectOutput(reporter, stringComplex.get(),
240 "<0974657374202920737472696E67202820666F6F>");
242 SkAutoTUnref<SkPDFName> name(new SkPDFName("Test name\twith#tab"));
243 const char expectedResult[] = "/Test#20name#09with#23tab";
244 CheckObjectOutput(reporter, name.get(), expectedResult,
245 strlen(expectedResult), false);
247 SkAutoTUnref<SkPDFName> escapedName(new SkPDFName("A#/%()<>[]{}B"));
248 const char escapedNameExpected[] = "/A#23#2F#25#28#29#3C#3E#5B#5D#7B#7DB";
249 CheckObjectOutput(reporter, escapedName.get(), escapedNameExpected,
250 strlen(escapedNameExpected), false);
252 // Test that we correctly handle characters with the high-bit set.
253 const unsigned char highBitCString[] = {0xDE, 0xAD, 'b', 'e', 0xEF, 0};
254 SkAutoTUnref<SkPDFName> highBitName(
255 new SkPDFName((const char*)highBitCString));
256 const char highBitExpectedResult[] = "/#DE#ADbe#EF";
257 CheckObjectOutput(reporter, highBitName.get(), highBitExpectedResult,
258 strlen(highBitExpectedResult), false);
260 SkAutoTUnref<SkPDFArray> array(new SkPDFArray);
261 SimpleCheckObjectOutput(reporter, array.get(), "[]");
262 array->append(int42.get());
263 SimpleCheckObjectOutput(reporter, array.get(), "[42]");
264 array->append(realHalf.get());
265 SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5]");
266 SkAutoTUnref<SkPDFInt> int0(new SkPDFInt(0));
267 array->append(int0.get());
268 SimpleCheckObjectOutput(reporter, array.get(), "[42 0.5 0]");
269 SkAutoTUnref<SkPDFInt> int1(new SkPDFInt(1));
270 array->setAt(0, int1.get());
271 SimpleCheckObjectOutput(reporter, array.get(), "[1 0.5 0]");
273 SkAutoTUnref<SkPDFDict> dict(new SkPDFDict);
274 SimpleCheckObjectOutput(reporter, dict.get(), "<<>>");
275 SkAutoTUnref<SkPDFName> n1(new SkPDFName("n1"));
276 dict->insert(n1.get(), int42.get());
277 SimpleCheckObjectOutput(reporter, dict.get(), "<</n1 42\n>>");
278 SkAutoTUnref<SkPDFName> n2(new SkPDFName("n2"));
279 SkAutoTUnref<SkPDFName> n3(new SkPDFName("n3"));
280 dict->insert(n2.get(), realHalf.get());
281 dict->insert(n3.get(), array.get());
282 SimpleCheckObjectOutput(reporter, dict.get(),
283 "<</n1 42\n/n2 0.5\n/n3 [1 0.5 0]\n>>");
285 TestPDFStream(reporter);
287 TestCatalog(reporter);
289 TestObjectRef(reporter);
291 TestSubstitute(reporter);
298 class DummyImageFilter : public SkImageFilter {
300 DummyImageFilter(bool visited = false) : SkImageFilter(0, NULL), fVisited(visited) {}
301 ~DummyImageFilter() override {}
302 virtual bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
303 SkBitmap* result, SkIPoint* offset) const override {
305 offset->fX = offset->fY = 0;
309 SK_TO_STRING_OVERRIDE()
310 SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(DummyImageFilter)
311 bool visited() const { return fVisited; }
314 mutable bool fVisited;
317 SkFlattenable* DummyImageFilter::CreateProc(SkReadBuffer& buffer) {
318 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 0);
319 bool visited = buffer.readBool();
320 return SkNEW_ARGS(DummyImageFilter, (visited));
323 #ifndef SK_IGNORE_TO_STRING
324 void DummyImageFilter::toString(SkString* str) const {
325 str->appendf("DummyImageFilter: (");
332 // Check that PDF rendering of image filters successfully falls back to
333 // CPU rasterization.
334 DEF_TEST(PDFImageFilter, reporter) {
335 SkDynamicMemoryWStream stream;
336 SkAutoTUnref<SkDocument> doc(SkDocument::CreatePDF(&stream));
337 SkCanvas* canvas = doc->beginPage(100.0f, 100.0f);
339 SkAutoTUnref<DummyImageFilter> filter(new DummyImageFilter());
341 // Filter just created; should be unvisited.
342 REPORTER_ASSERT(reporter, !filter->visited());
344 paint.setImageFilter(filter.get());
345 canvas->drawRect(SkRect::MakeWH(100, 100), paint);
348 // Filter was used in rendering; should be visited.
349 REPORTER_ASSERT(reporter, filter->visited());