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