1 /*
2  * Copyright 2020 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 "tests/Test.h"
8 
9 #ifdef SK_SUPPORT_PDF
10 
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/SkPaint.h"
16 #include "include/core/SkRefCnt.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkStream.h"
19 #include "include/core/SkString.h"
20 #include "include/core/SkTime.h"
21 #include "include/core/SkTypeface.h"
22 #include "include/docs/SkPDFDocument.h"
23 
24 #include <memory>
25 #include <utility>
26 #include <vector>
27 
28 using PDFTag = SkPDF::StructureElementNode;
29 
30 // Test building a tagged PDF containing a table.
31 // Add this to args.gn to output the PDF to a file:
32 //   extra_cflags = [ "-DSK_PDF_TEST_TAGS_OUTPUT_PATH=\"/tmp/table.pdf\"" ]
DEF_TEST(SkPDF_tagged_table,r)33 DEF_TEST(SkPDF_tagged_table, r) {
34     REQUIRE_PDF_DOCUMENT(SkPDF_tagged, r);
35 #ifdef SK_PDF_TEST_TAGS_OUTPUT_PATH
36     SkFILEWStream outputStream(SK_PDF_TEST_TAGS_OUTPUT_PATH);
37 #else
38     SkDynamicMemoryWStream outputStream;
39 #endif
40 
41     SkSize pageSize = SkSize::Make(612, 792);  // U.S. Letter
42 
43     SkPDF::Metadata metadata;
44     metadata.fTitle = "Example Tagged Table PDF";
45     metadata.fCreator = "Skia";
46     SkTime::DateTime now;
47     SkTime::GetDateTime(&now);
48     metadata.fCreation = now;
49     metadata.fModified = now;
50 
51     constexpr int kRowCount = 5;
52     constexpr int kColCount = 4;
53     const char* cellData[kRowCount * kColCount] = {
54         "Car",                  "Engine",   "City MPG", "Highway MPG",
55         "Mitsubishi Mirage ES", "Gas",      "28",       "47",
56         "Toyota Prius Three",   "Hybrid",   "43",       "59",
57         "Nissan Leaf SL",       "Electric", "N/A",      nullptr,
58         "Tesla Model 3",        nullptr,    "N/A",      nullptr
59     };
60 
61     // The document tag.
62     auto root = std::make_unique<PDFTag>();
63     root->fNodeId = 1;
64     root->fTypeString = "Document";
65     root->fLang = "en-US";
66 
67     // Heading.
68     auto h1 = std::make_unique<PDFTag>();
69     h1->fNodeId = 2;
70     h1->fTypeString = "H1";
71     h1->fAlt = "Tagged PDF Table Alt Text";
72     root->fChildVector.push_back(std::move(h1));
73 
74     // Table.
75     auto table = std::make_unique<PDFTag>();
76     table->fNodeId = 3;
77     table->fTypeString = "Table";
78     auto& rows = table->fChildVector;
79     table->fAttributes.appendFloatArray("Layout", "BBox", {72, 72, 360, 360});
80 
81     for (int rowIndex = 0; rowIndex < kRowCount; rowIndex++) {
82         auto row = std::make_unique<PDFTag>();
83         row->fNodeId = 4 + rowIndex;
84         row->fTypeString = "TR";
85         auto& cells = row->fChildVector;
86         for (int colIndex = 0; colIndex < kColCount; colIndex++) {
87             auto cell = std::make_unique<PDFTag>();
88             int cellIndex = rowIndex * kColCount + colIndex;
89             cell->fNodeId = 10 + cellIndex;
90             if (!cellData[cellIndex]) {
91                 cell->fTypeString = "NonStruct";
92             } else if (rowIndex == 0 || colIndex == 0) {
93                 cell->fTypeString = "TH";
94             } else {
95                 cell->fTypeString = "TD";
96                 std::vector<int> headerIds;
97                 headerIds.push_back(10 + rowIndex * kColCount);  // Row header
98                 headerIds.push_back(10 + colIndex);  // Col header.
99                 cell->fAttributes.appendNodeIdArray(
100                     "Table", "Headers", headerIds);
101             }
102 
103             if (cellIndex == 13) {
104                 cell->fAttributes.appendInt("Table", "RowSpan", 2);
105             } else if (cellIndex == 14 || cellIndex == 18) {
106                 cell->fAttributes.appendInt("Table", "ColSpan", 2);
107             } else if (rowIndex == 0 || colIndex == 0) {
108                 cell->fAttributes.appendName(
109                     "Table", "Scope", rowIndex == 0 ? "Column" : "Row");
110             }
111             cells.push_back(std::move(cell));
112         }
113         rows.push_back(std::move(row));
114     }
115     root->fChildVector.push_back(std::move(table));
116 
117     metadata.fStructureElementTreeRoot = root.get();
118     sk_sp<SkDocument> document = SkPDF::MakeDocument(
119         &outputStream, metadata);
120 
121     SkPaint paint;
122     paint.setColor(SK_ColorBLACK);
123 
124     SkCanvas* canvas =
125             document->beginPage(pageSize.width(),
126                                 pageSize.height());
127     SkPDF::SetNodeId(canvas, 2);
128     SkFont font(nullptr, 36);
129     canvas->drawString("Tagged PDF Table", 72, 72, font, paint);
130 
131     font.setSize(14);
132     for (int rowIndex = 0; rowIndex < kRowCount; rowIndex++) {
133         for (int colIndex = 0; colIndex < kColCount; colIndex++) {
134             int cellIndex = rowIndex * kColCount + colIndex;
135             const char* str = cellData[cellIndex];
136             if (!str)
137                 continue;
138 
139             int x = 72 + colIndex * 108 + (colIndex > 0 ? 72 : 0);
140             int y = 144 + rowIndex * 48;
141 
142             SkPDF::SetNodeId(canvas, 10 + cellIndex);
143             canvas->drawString(str, x, y, font, paint);
144         }
145     }
146 
147     document->endPage();
148     document->close();
149     outputStream.flush();
150 }
151 
152 #endif
153