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 
16 #include "sk_tool_utils.h"
17 
18 static void test_empty(skiatest::Reporter* reporter) {
19     SkDynamicMemoryWStream stream;
20 
21     sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
22 
23     doc->close();
24 
25     REPORTER_ASSERT(reporter, stream.bytesWritten() == 0);
26 }
27 
28 static void test_abort(skiatest::Reporter* reporter) {
29     SkDynamicMemoryWStream stream;
30     sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
31 
32     SkCanvas* canvas = doc->beginPage(100, 100);
33     canvas->drawColor(SK_ColorRED);
34     doc->endPage();
35 
36     doc->abort();
37 
38     // Test that only the header is written, not the full document.
39     REPORTER_ASSERT(reporter, stream.bytesWritten() < 256);
40 }
41 
42 static void test_abortWithFile(skiatest::Reporter* reporter) {
43     SkString tmpDir = skiatest::GetTmpDir();
44 
45     if (tmpDir.isEmpty()) {
46         ERRORF(reporter, "missing tmpDir.");
47         return;
48     }
49 
50     SkString path = SkOSPath::Join(tmpDir.c_str(), "aborted.pdf");
51     if (!SkFILEWStream(path.c_str()).isValid()) {
52         ERRORF(reporter, "unable to write to: %s", path.c_str());
53         return;
54     }
55 
56     // Make sure doc's destructor is called to flush.
57     {
58         SkFILEWStream stream(path.c_str());
59         sk_sp<SkDocument> doc = SkDocument::MakePDF(&stream);
60 
61         SkCanvas* canvas = doc->beginPage(100, 100);
62         canvas->drawColor(SK_ColorRED);
63         doc->endPage();
64 
65         doc->abort();
66     }
67 
68     FILE* file = fopen(path.c_str(), "r");
69     // Test that only the header is written, not the full document.
70     char buffer[256];
71     REPORTER_ASSERT(reporter, fread(buffer, 1, sizeof(buffer), file) < sizeof(buffer));
72     fclose(file);
73 }
74 
75 static void test_file(skiatest::Reporter* reporter) {
76     SkString tmpDir = skiatest::GetTmpDir();
77     if (tmpDir.isEmpty()) {
78         ERRORF(reporter, "missing tmpDir.");
79         return;
80     }
81 
82     SkString path = SkOSPath::Join(tmpDir.c_str(), "file.pdf");
83     if (!SkFILEWStream(path.c_str()).isValid()) {
84         ERRORF(reporter, "unable to write to: %s", path.c_str());
85         return;
86     }
87 
88     {
89         SkFILEWStream stream(path.c_str());
90         sk_sp<SkDocument> doc = SkDocument::MakePDF(&stream);
91         SkCanvas* canvas = doc->beginPage(100, 100);
92 
93         canvas->drawColor(SK_ColorRED);
94         doc->endPage();
95         doc->close();
96     }
97 
98     FILE* file = fopen(path.c_str(), "r");
99     REPORTER_ASSERT(reporter, file != nullptr);
100     char header[100];
101     REPORTER_ASSERT(reporter, fread(header, 4, 1, file) != 0);
102     REPORTER_ASSERT(reporter, strncmp(header, "%PDF", 4) == 0);
103     fclose(file);
104 }
105 
106 static void test_close(skiatest::Reporter* reporter) {
107     SkDynamicMemoryWStream stream;
108     sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
109 
110     SkCanvas* canvas = doc->beginPage(100, 100);
111     canvas->drawColor(SK_ColorRED);
112     doc->endPage();
113 
114     doc->close();
115 
116     REPORTER_ASSERT(reporter, stream.bytesWritten() != 0);
117 }
118 
119 DEF_TEST(SkPDF_document_tests, reporter) {
120     REQUIRE_PDF_DOCUMENT(document_tests, reporter);
121     test_empty(reporter);
122     test_abort(reporter);
123     test_abortWithFile(reporter);
124     test_file(reporter);
125     test_close(reporter);
126 }
127 
128 DEF_TEST(SkPDF_document_skbug_4734, r) {
129     REQUIRE_PDF_DOCUMENT(SkPDF_document_skbug_4734, r);
130     SkDynamicMemoryWStream stream;
131     sk_sp<SkDocument> doc(SkDocument::MakePDF(&stream));
132     SkCanvas* canvas = doc->beginPage(64, 64);
133     canvas->scale(10000.0f, 10000.0f);
134     canvas->translate(20.0f, 10.0f);
135     canvas->rotate(30.0f);
136     const char text[] = "HELLO";
137     canvas->drawString(text, 0, 0, SkPaint());
138 }
139 
140 static bool contains(const uint8_t* result, size_t size, const char expectation[]) {
141     size_t len = strlen(expectation);
142     size_t N = 1 + size - len;
143     for (size_t i = 0; i < N; ++i) {
144         if (0 == memcmp(result + i, expectation, len)) {
145             return true;
146         }
147     }
148     return false;
149 }
150 
151 // verify that the PDFA flag does something.
152 DEF_TEST(SkPDF_pdfa_document, r) {
153     REQUIRE_PDF_DOCUMENT(SkPDF_pdfa_document, r);
154 
155     SkDocument::PDFMetadata pdfMetadata;
156     pdfMetadata.fTitle = "test document";
157     pdfMetadata.fCreation.fEnabled = true;
158     pdfMetadata.fCreation.fDateTime = {0, 1999, 12, 5, 31, 23, 59, 59};
159     pdfMetadata.fPDFA = true;
160 
161     SkDynamicMemoryWStream buffer;
162     auto doc = SkDocument::MakePDF(&buffer, pdfMetadata);
163     doc->beginPage(64, 64)->drawColor(SK_ColorRED);
164     doc->close();
165     sk_sp<SkData> data(buffer.detachAsData());
166 
167     static const char* expectations[] = {
168         "sRGB IEC61966-2.1",
169         "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">test document",
170         "<xmp:CreateDate>1999-12-31T23:59:59+00:00</xmp:CreateDate>",
171         "/Subtype /XML",
172         "/CreationDate (D:19991231235959+00'00')>>",
173     };
174     for (const char* expectation : expectations) {
175         if (!contains(data->bytes(), data->size(), expectation)) {
176             ERRORF(r, "PDFA expectation missing: '%s'.", expectation);
177         }
178     }
179     pdfMetadata.fProducer = "phoney library";
180     pdfMetadata.fPDFA = true;
181     doc = SkDocument::MakePDF(&buffer, pdfMetadata);
182     doc->beginPage(64, 64)->drawColor(SK_ColorRED);
183     doc->close();
184     data = buffer.detachAsData();
185 
186     static const char* moreExpectations[] = {
187         "/Producer (phoney library)",
188         "/ProductionLibrary (Skia/PDF m",
189         "<!-- <skia:ProductionLibrary>Skia/PDF m",
190         "<pdf:Producer>phoney library</pdf:Producer>",
191     };
192     for (const char* expectation : moreExpectations) {
193         if (!contains(data->bytes(), data->size(), expectation)) {
194             ERRORF(r, "PDFA expectation missing: '%s'.", expectation);
195         }
196     }
197 }
198