• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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->drawString(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 m",
216         "<!-- <skia:ProductionLibrary>Skia/PDF m",
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