1 /*
2 * Copyright 2013 Google Inc.
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 #include "Test.h"
8
9 #include "Resources.h"
10 #include "SkCanvas.h"
11 #include "SkDocument.h"
12 #include "SkOSFile.h"
13 #include "SkOSPath.h"
14 #include "SkStream.h"
15 #include "SkPixelSerializer.h"
16
17 #include "sk_tool_utils.h"
18
test_empty(skiatest::Reporter * reporter)19 static void test_empty(skiatest::Reporter* reporter) {
20 SkDynamicMemoryWStream stream;
21
22 sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
23
24 doc->close();
25
26 REPORTER_ASSERT(reporter, stream.bytesWritten() == 0);
27 }
28
test_abort(skiatest::Reporter * reporter)29 static void test_abort(skiatest::Reporter* reporter) {
30 SkDynamicMemoryWStream stream;
31 sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
32
33 SkCanvas* canvas = doc->beginPage(100, 100);
34 canvas->drawColor(SK_ColorRED);
35 doc->endPage();
36
37 doc->abort();
38
39 // Test that only the header is written, not the full document.
40 REPORTER_ASSERT(reporter, stream.bytesWritten() < 256);
41 }
42
test_abortWithFile(skiatest::Reporter * reporter)43 static void test_abortWithFile(skiatest::Reporter* reporter) {
44 SkString tmpDir = skiatest::GetTmpDir();
45
46 if (tmpDir.isEmpty()) {
47 return; // TODO(edisonn): unfortunatelly this pattern is used in other
48 // tests, but if GetTmpDir() starts returning and empty dir
49 // allways, then all these tests will be disabled.
50 }
51
52 SkString path = SkOSPath::Join(tmpDir.c_str(), "aborted.pdf");
53
54 // Make sure doc's destructor is called to flush.
55 {
56 sk_sp<SkDocument> doc(SkDocument::MakePDF(path.c_str()));
57
58 SkCanvas* canvas = doc->beginPage(100, 100);
59 canvas->drawColor(SK_ColorRED);
60 doc->endPage();
61
62 doc->abort();
63 }
64
65 FILE* file = fopen(path.c_str(), "r");
66 // Test that only the header is written, not the full document.
67 char buffer[256];
68 REPORTER_ASSERT(reporter, fread(buffer, 1, sizeof(buffer), file) < sizeof(buffer));
69 fclose(file);
70 }
71
test_file(skiatest::Reporter * reporter)72 static void test_file(skiatest::Reporter* reporter) {
73 SkString tmpDir = skiatest::GetTmpDir();
74 if (tmpDir.isEmpty()) {
75 return; // TODO(edisonn): unfortunatelly this pattern is used in other
76 // tests, but if GetTmpDir() starts returning and empty dir
77 // allways, then all these tests will be disabled.
78 }
79
80 SkString path = SkOSPath::Join(tmpDir.c_str(), "file.pdf");
81
82 sk_sp<SkDocument> doc(SkDocument::MakePDF(path.c_str()));
83
84 SkCanvas* canvas = doc->beginPage(100, 100);
85
86 canvas->drawColor(SK_ColorRED);
87 doc->endPage();
88 doc->close();
89
90 FILE* file = fopen(path.c_str(), "r");
91 REPORTER_ASSERT(reporter, file != nullptr);
92 char header[100];
93 REPORTER_ASSERT(reporter, fread(header, 4, 1, file) != 0);
94 REPORTER_ASSERT(reporter, strncmp(header, "%PDF", 4) == 0);
95 fclose(file);
96 }
97
test_close(skiatest::Reporter * reporter)98 static void test_close(skiatest::Reporter* reporter) {
99 SkDynamicMemoryWStream stream;
100 sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
101
102 SkCanvas* canvas = doc->beginPage(100, 100);
103 canvas->drawColor(SK_ColorRED);
104 doc->endPage();
105
106 doc->close();
107
108 REPORTER_ASSERT(reporter, stream.bytesWritten() != 0);
109 }
110
DEF_TEST(SkPDF_document_tests,reporter)111 DEF_TEST(SkPDF_document_tests, reporter) {
112 REQUIRE_PDF_DOCUMENT(document_tests, reporter);
113 test_empty(reporter);
114 test_abort(reporter);
115 test_abortWithFile(reporter);
116 test_file(reporter);
117 test_close(reporter);
118 }
119
120 namespace {
121 class JPEGSerializer final : public SkPixelSerializer {
onUseEncodedData(const void *,size_t)122 bool onUseEncodedData(const void*, size_t) override { return true; }
onEncode(const SkPixmap & pixmap)123 SkData* onEncode(const SkPixmap& pixmap) override {
124 return sk_tool_utils::EncodeImageToData(pixmap, SkEncodedImageFormat::kJPEG, 85).release();
125 }
126 };
127 } // namespace
128
count_bytes(const SkBitmap & bm,bool useDCT)129 size_t count_bytes(const SkBitmap& bm, bool useDCT) {
130 SkDynamicMemoryWStream stream;
131 sk_sp<SkDocument> doc;
132 if (useDCT) {
133 doc = SkDocument::MakePDF(&stream, SK_ScalarDefaultRasterDPI,
134 SkDocument::PDFMetadata(),
135 sk_make_sp<JPEGSerializer>(), false);
136 } else {
137 doc = SkDocument::MakePDF(&stream);
138 }
139 SkCanvas* canvas = doc->beginPage(64, 64);
140 canvas->drawBitmap(bm, 0, 0);
141 doc->endPage();
142 doc->close();
143 return stream.bytesWritten();
144 }
145
DEF_TEST(SkPDF_document_dct_encoder,r)146 DEF_TEST(SkPDF_document_dct_encoder, r) {
147 REQUIRE_PDF_DOCUMENT(SkPDF_document_dct_encoder, r);
148 SkBitmap bm;
149 if (GetResourceAsBitmap("mandrill_64.png", &bm)) {
150 // Lossy encoding works better on photographs.
151 REPORTER_ASSERT(r, count_bytes(bm, true) < count_bytes(bm, false));
152 }
153 }
154
DEF_TEST(SkPDF_document_skbug_4734,r)155 DEF_TEST(SkPDF_document_skbug_4734, r) {
156 REQUIRE_PDF_DOCUMENT(SkPDF_document_skbug_4734, r);
157 SkDynamicMemoryWStream stream;
158 sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
159 SkCanvas* canvas = doc->beginPage(64, 64);
160 canvas->scale(10000.0f, 10000.0f);
161 canvas->translate(20.0f, 10.0f);
162 canvas->rotate(30.0f);
163 const char text[] = "HELLO";
164 canvas->drawText(text, strlen(text), 0, 0, SkPaint());
165 }
166
contains(const uint8_t * result,size_t size,const char expectation[])167 static bool contains(const uint8_t* result, size_t size, const char expectation[]) {
168 size_t len = strlen(expectation);
169 size_t N = 1 + size - len;
170 for (size_t i = 0; i < N; ++i) {
171 if (0 == memcmp(result + i, expectation, len)) {
172 return true;
173 }
174 }
175 return false;
176 }
177
178 // verify that the PDFA flag does something.
DEF_TEST(SkPDF_pdfa_document,r)179 DEF_TEST(SkPDF_pdfa_document, r) {
180 REQUIRE_PDF_DOCUMENT(SkPDF_pdfa_document, r);
181
182 SkDocument::PDFMetadata pdfMetadata;
183 pdfMetadata.fTitle = "test document";
184 pdfMetadata.fCreation.fEnabled = true;
185 pdfMetadata.fCreation.fDateTime = {0, 1999, 12, 5, 31, 23, 59, 59};
186
187 SkDynamicMemoryWStream buffer;
188 auto doc = SkDocument::MakePDF(&buffer, SK_ScalarDefaultRasterDPI,
189 pdfMetadata, nullptr, /* pdfa = */ true);
190 doc->beginPage(64, 64)->drawColor(SK_ColorRED);
191 doc->close();
192 sk_sp<SkData> data(buffer.detachAsData());
193
194 static const char* expectations[] = {
195 "sRGB IEC61966-2.1",
196 "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">test document",
197 "<xmp:CreateDate>1999-12-31T23:59:59+00:00</xmp:CreateDate>",
198 "/Subtype /XML",
199 "/CreationDate (D:19991231235959+00'00')>>",
200 };
201 for (const char* expectation : expectations) {
202 if (!contains(data->bytes(), data->size(), expectation)) {
203 ERRORF(r, "PDFA expectation missing: '%s'.", expectation);
204 }
205 }
206 pdfMetadata.fProducer = "phoney library";
207 doc = SkDocument::MakePDF(&buffer, SK_ScalarDefaultRasterDPI,
208 pdfMetadata, nullptr, /* pdfa = */ true);
209 doc->beginPage(64, 64)->drawColor(SK_ColorRED);
210 doc->close();
211 data = buffer.detachAsData();
212
213 static const char* moreExpectations[] = {
214 "/Producer (phoney library)",
215 "/ProductionLibrary (Skia/PDF ",
216 "<!-- <skia:ProductionLibrary>Skia/PDF ",
217 "<pdf:Producer>phoney library</pdf:Producer>",
218 };
219 for (const char* expectation : moreExpectations) {
220 if (!contains(data->bytes(), data->size(), expectation)) {
221 ERRORF(r, "PDFA expectation missing: '%s'.", expectation);
222 }
223 }
224 }
225