1 /*
2 * Copyright 2018 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 "include/core/SkTypes.h"
8
9 #ifdef SK_SUPPORT_PDF
10 #include "include/core/SkBitmap.h"
11 #include "include/core/SkCanvas.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkDocument.h"
14 #include "include/core/SkFont.h"
15 #include "include/core/SkImage.h" // IWYU pragma: keep
16 #include "include/core/SkPaint.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkSize.h"
19 #include "include/core/SkStream.h"
20 #include "include/core/SkString.h"
21 #include "include/core/SkTypeface.h"
22 #include "include/docs/SkPDFDocument.h"
23 #include "include/docs/SkPDFJpegHelpers.h"
24 #include "src/pdf/SkPDFUtils.h"
25 #include "tests/Test.h"
26 #include "tools/fonts/FontToolUtils.h"
27
28 #include <memory>
29 #include <utility>
30 #include <vector>
31
32 using PDFTag = SkPDF::StructureElementNode;
33
34 // Test building a tagged PDF.
35 // Add this to args.gn to output the PDF to a file:
36 // extra_cflags = [ "-DSK_PDF_TEST_TAGS_OUTPUT_PATH=\"/tmp/foo.pdf\"" ]
DEF_TEST(SkPDF_tagged_doc,r)37 DEF_TEST(SkPDF_tagged_doc, r) {
38 REQUIRE_PDF_DOCUMENT(SkPDF_tagged_doc, r);
39 #ifdef SK_PDF_TEST_TAGS_OUTPUT_PATH
40 SkFILEWStream outputStream(SK_PDF_TEST_TAGS_OUTPUT_PATH);
41 #else
42 SkDynamicMemoryWStream outputStream;
43 #endif
44
45 SkSize pageSize = SkSize::Make(612, 792); // U.S. Letter
46
47 SkPDF::Metadata metadata;
48 metadata.fTitle = "Example Tagged PDF";
49 metadata.fCreator = "Skia";
50 metadata.fOutline = SkPDF::Metadata::Outline::StructureElementHeaders;
51 SkPDF::DateTime now;
52 SkPDFUtils::GetDateTime(&now);
53 metadata.fCreation = now;
54 metadata.fModified = now;
55 metadata.jpegDecoder = SkPDF::JPEG::Decode;
56 metadata.jpegEncoder = SkPDF::JPEG::Encode;
57
58 // The document tag.
59 auto root = std::make_unique<PDFTag>();
60 root->fNodeId = 1;
61 root->fTypeString = "Document";
62
63 // Heading.
64 auto h1 = std::make_unique<PDFTag>();
65 h1->fNodeId = 2;
66 h1->fTypeString = "H1";
67 h1->fAlt = "A Header";
68 root->fChildVector.push_back(std::move(h1));
69
70 // Initial paragraph.
71 auto p = std::make_unique<PDFTag>();
72 p->fNodeId = 3;
73 p->fTypeString = "P";
74 root->fChildVector.push_back(std::move(p));
75
76 // Hidden div. This is never referenced by marked content
77 // so it should not appear in the resulting PDF.
78 auto div = std::make_unique<PDFTag>();
79 div->fNodeId = 4;
80 div->fTypeString = "Div";
81 root->fChildVector.push_back(std::move(div));
82
83 // A bulleted list of two items.
84 auto l = std::make_unique<PDFTag>();
85 l->fNodeId = 5;
86 l->fTypeString = "L";
87
88 auto lm1 = std::make_unique<PDFTag>();
89 lm1->fNodeId = 6;
90 lm1->fTypeString = "Lbl";
91 l->fChildVector.push_back(std::move(lm1));
92
93 auto li1 = std::make_unique<PDFTag>();
94 li1->fNodeId = 7;
95 li1->fTypeString = "LI";
96 l->fChildVector.push_back(std::move(li1));
97
98 auto lm2 = std::make_unique<PDFTag>();
99 lm2->fNodeId = 8;
100 lm2->fTypeString = "Lbl";
101 l->fChildVector.push_back(std::move(lm2));
102 auto li2 = std::make_unique<PDFTag>();
103 li2->fNodeId = 9;
104 li2->fTypeString = "LI";
105 l->fChildVector.push_back(std::move(li2));
106
107 root->fChildVector.push_back(std::move(l));
108
109 // Paragraph spanning two pages.
110 auto p2 = std::make_unique<PDFTag>();
111 p2->fNodeId = 10;
112 p2->fTypeString = "P";
113 root->fChildVector.push_back(std::move(p2));
114
115 // Image with alt text.
116 auto img = std::make_unique<PDFTag>();
117 img->fNodeId = 11;
118 img->fTypeString = "Figure";
119 img->fAlt = "Red box";
120 root->fChildVector.push_back(std::move(img));
121
122 metadata.fStructureElementTreeRoot = root.get();
123 sk_sp<SkDocument> document = SkPDF::MakeDocument(
124 &outputStream, metadata);
125
126 SkPaint paint;
127 paint.setColor(SK_ColorBLACK);
128
129 // First page.
130 SkCanvas* canvas =
131 document->beginPage(pageSize.width(),
132 pageSize.height());
133 SkPDF::SetNodeId(canvas, 2);
134 SkFont font(ToolUtils::DefaultTypeface(), 36);
135 const char* message = "This is the title";
136 canvas->translate(72, 72);
137 canvas->drawString(message, 0, 0, font, paint);
138
139 SkPDF::SetNodeId(canvas, 3);
140 font.setSize(14);
141 message = "This is a simple paragraph.";
142 canvas->translate(0, 72);
143 canvas->drawString(message, 0, 0, font, paint);
144
145 SkPDF::SetNodeId(canvas, 6);
146 font.setSize(14);
147 message = "*";
148 canvas->translate(0, 72);
149 canvas->drawString(message, 0, 0, font, paint);
150
151 SkPDF::SetNodeId(canvas, 7);
152 message = "List item 1";
153 canvas->translate(36, 0);
154 canvas->drawString(message, 0, 0, font, paint);
155
156 SkPDF::SetNodeId(canvas, 8);
157 message = "*";
158 canvas->translate(-36, 36);
159 canvas->drawString(message, 0, 0, font, paint);
160
161 SkPDF::SetNodeId(canvas, 9);
162 message = "List item 2";
163 canvas->translate(36, 0);
164 canvas->drawString(message, 0, 0, font, paint);
165
166 SkPDF::SetNodeId(canvas, 10);
167 message = "This is a paragraph that starts on one page";
168 canvas->translate(-36, 6 * 72);
169 canvas->drawString(message, 0, 0, font, paint);
170
171 document->endPage();
172
173 // Second page.
174 canvas = document->beginPage(pageSize.width(),
175 pageSize.height());
176 SkPDF::SetNodeId(canvas, 10);
177 message = "and finishes on the second page.";
178 canvas->translate(72, 72);
179 canvas->drawString(message, 0, 0, font, paint);
180
181 // Test a tagged image with alt text.
182 SkPDF::SetNodeId(canvas, 11);
183 SkBitmap testBitmap;
184 testBitmap.allocN32Pixels(72, 72);
185 testBitmap.eraseColor(SK_ColorRED);
186 canvas->translate(72, 72);
187 canvas->drawImage(testBitmap.asImage(), 0, 0);
188
189 // This has a node ID but never shows up in the tag tree so it
190 // won't be tagged.
191 SkPDF::SetNodeId(canvas, 999);
192 message = "Page 2";
193 canvas->translate(468, -36);
194 canvas->drawString(message, 0, 0, font, paint);
195
196 document->endPage();
197
198 document->close();
199
200 outputStream.flush();
201 }
202 #endif
203