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
19static 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
29static 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
43static 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
72static 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
98static 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
111DEF_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
120namespace {
121class JPEGSerializer final : public SkPixelSerializer {
122    bool onUseEncodedData(const void*, size_t) override { return true; }
123    SkData* onEncode(const SkPixmap& pixmap) override {
124        return sk_tool_utils::EncodeImageToData(pixmap, SkEncodedImageFormat::kJPEG, 85).release();
125    }
126};
127}  // namespace
128
129size_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
146DEF_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
155DEF_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
167static 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.
179DEF_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