1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "printing/common/metafile_utils.h"
6 
7 #include "base/strings/stringprintf.h"
8 #include "base/time/time.h"
9 #include "printing/buildflags/buildflags.h"
10 #include "third_party/skia/include/core/SkCanvas.h"
11 #include "third_party/skia/include/core/SkPicture.h"
12 #include "third_party/skia/include/core/SkPictureRecorder.h"
13 #include "third_party/skia/include/core/SkTime.h"
14 #include "third_party/skia/include/docs/SkPDFDocument.h"
15 #include "ui/accessibility/ax_node.h"
16 #include "ui/accessibility/ax_node_data.h"
17 #include "ui/accessibility/ax_role_properties.h"
18 #include "ui/accessibility/ax_tree.h"
19 #include "ui/accessibility/ax_tree_update.h"
20 
21 namespace {
22 
23 #if BUILDFLAG(ENABLE_TAGGED_PDF)
24 
25 // Table 333 in PDF 32000-1:2008 spec, section 14.8.4.2
26 const char kPDFStructureTypeDocument[] = "Document";
27 const char kPDFStructureTypeParagraph[] = "P";
28 const char kPDFStructureTypeDiv[] = "Div";
29 const char kPDFStructureTypeHeading[] = "H";
30 const char kPDFStructureTypeLink[] = "Link";
31 const char kPDFStructureTypeList[] = "L";
32 const char kPDFStructureTypeListItemLabel[] = "Lbl";
33 const char kPDFStructureTypeListItemBody[] = "LI";
34 const char kPDFStructureTypeTable[] = "Table";
35 const char kPDFStructureTypeTableRow[] = "TR";
36 const char kPDFStructureTypeTableHeader[] = "TH";
37 const char kPDFStructureTypeTableCell[] = "TD";
38 const char kPDFStructureTypeFigure[] = "Figure";
39 const char kPDFStructureTypeNonStruct[] = "NonStruct";
40 
41 // Standard attribute owners from PDF 32000-1:2008 spec, section 14.8.5.2
42 // (Attribute owners are kind of like "categories" for structure node
43 // attributes.)
44 const char kPDFTableAttributeOwner[] = "Table";
45 
46 // Table Attributes from PDF 32000-1:2008 spec, section 14.8.5.7
47 const char kPDFTableCellColSpanAttribute[] = "ColSpan";
48 const char kPDFTableCellHeadersAttribute[] = "Headers";
49 const char kPDFTableCellRowSpanAttribute[] = "RowSpan";
50 const char kPDFTableHeaderScopeAttribute[] = "Scope";
51 const char kPDFTableHeaderScopeColumn[] = "Column";
52 const char kPDFTableHeaderScopeRow[] = "Row";
53 
GetHeadingStructureType(int heading_level)54 SkString GetHeadingStructureType(int heading_level) {
55   // From Table 333 in PDF 32000-1:2008 spec, section 14.8.4.2,
56   // "H1"..."H6" are valid structure types.
57   if (heading_level >= 1 && heading_level <= 6)
58     return SkString(base::StringPrintf("H%d", heading_level).c_str());
59 
60   // If we don't have a valid heading level, use the generic heading role.
61   return SkString(kPDFStructureTypeHeading);
62 }
63 
64 #endif  // BUILDFLAG(ENABLE_TAGGED_PDF)
65 
TimeToSkTime(base::Time time)66 SkTime::DateTime TimeToSkTime(base::Time time) {
67   base::Time::Exploded exploded;
68   time.UTCExplode(&exploded);
69   SkTime::DateTime skdate;
70   skdate.fTimeZoneMinutes = 0;
71   skdate.fYear = exploded.year;
72   skdate.fMonth = exploded.month;
73   skdate.fDayOfWeek = exploded.day_of_week;
74   skdate.fDay = exploded.day_of_month;
75   skdate.fHour = exploded.hour;
76   skdate.fMinute = exploded.minute;
77   skdate.fSecond = exploded.second;
78   return skdate;
79 }
80 
GetEmptyPicture()81 sk_sp<SkPicture> GetEmptyPicture() {
82   SkPictureRecorder rec;
83   SkCanvas* canvas = rec.beginRecording(100, 100);
84   // Add some ops whose net effects equal to a noop.
85   canvas->save();
86   canvas->restore();
87   return rec.finishRecordingAsPicture();
88 }
89 
90 // Convert an AXNode into a SkPDF::StructureElementNode in order to make a
91 // tagged (accessible) PDF. Returns true on success and false if we don't
92 // have enough data to build a valid tree.
RecursiveBuildStructureTree(const ui::AXNode * ax_node,SkPDF::StructureElementNode * tag)93 bool RecursiveBuildStructureTree(const ui::AXNode* ax_node,
94                                  SkPDF::StructureElementNode* tag) {
95 #if BUILDFLAG(ENABLE_TAGGED_PDF)
96   bool valid = false;
97 
98   tag->fNodeId = ax_node->GetIntAttribute(ax::mojom::IntAttribute::kDOMNodeId);
99   switch (ax_node->data().role) {
100     case ax::mojom::Role::kRootWebArea:
101       tag->fTypeString = kPDFStructureTypeDocument;
102       break;
103     case ax::mojom::Role::kParagraph:
104       tag->fTypeString = kPDFStructureTypeParagraph;
105       break;
106     case ax::mojom::Role::kGenericContainer:
107       tag->fTypeString = kPDFStructureTypeDiv;
108       break;
109     case ax::mojom::Role::kHeading:
110       tag->fTypeString = GetHeadingStructureType(ax_node->GetIntAttribute(
111           ax::mojom::IntAttribute::kHierarchicalLevel));
112       break;
113     case ax::mojom::Role::kLink:
114       tag->fTypeString = kPDFStructureTypeLink;
115       break;
116     case ax::mojom::Role::kList:
117       tag->fTypeString = kPDFStructureTypeList;
118       break;
119     case ax::mojom::Role::kListMarker:
120       tag->fTypeString = kPDFStructureTypeListItemLabel;
121       break;
122     case ax::mojom::Role::kListItem:
123       tag->fTypeString = kPDFStructureTypeListItemBody;
124       break;
125     case ax::mojom::Role::kTable:
126       tag->fTypeString = kPDFStructureTypeTable;
127       break;
128     case ax::mojom::Role::kRow:
129       tag->fTypeString = kPDFStructureTypeTableRow;
130       break;
131     case ax::mojom::Role::kColumnHeader:
132       tag->fTypeString = kPDFStructureTypeTableHeader;
133       tag->fAttributes.appendName(kPDFTableAttributeOwner,
134                                   kPDFTableHeaderScopeAttribute,
135                                   kPDFTableHeaderScopeColumn);
136       break;
137     case ax::mojom::Role::kRowHeader:
138       tag->fTypeString = kPDFStructureTypeTableHeader;
139       tag->fAttributes.appendName(kPDFTableAttributeOwner,
140                                   kPDFTableHeaderScopeAttribute,
141                                   kPDFTableHeaderScopeRow);
142       break;
143     case ax::mojom::Role::kCell: {
144       tag->fTypeString = kPDFStructureTypeTableCell;
145 
146       // Append an attribute consisting of the string IDs of all of the
147       // header cells that correspond to this table cell.
148       std::vector<ui::AXNode*> header_nodes;
149       ax_node->GetTableCellColHeaders(&header_nodes);
150       ax_node->GetTableCellRowHeaders(&header_nodes);
151       std::vector<int> header_ids;
152       header_ids.reserve(header_nodes.size());
153       for (ui::AXNode* header_node : header_nodes) {
154         header_ids.push_back(header_node->GetIntAttribute(
155             ax::mojom::IntAttribute::kDOMNodeId));
156       }
157       tag->fAttributes.appendNodeIdArray(
158           kPDFTableAttributeOwner, kPDFTableCellHeadersAttribute, header_ids);
159       break;
160     }
161     case ax::mojom::Role::kFigure:
162     case ax::mojom::Role::kImage: {
163       tag->fTypeString = kPDFStructureTypeFigure;
164       std::string alt =
165           ax_node->GetStringAttribute(ax::mojom::StringAttribute::kName);
166       tag->fAlt = SkString(alt.c_str());
167       break;
168     }
169     case ax::mojom::Role::kStaticText:
170       // Currently we're only marking text content, so we can't generate
171       // a nonempty structure tree unless we have at least one kStaticText
172       // node in the tree.
173       tag->fTypeString = kPDFStructureTypeNonStruct;
174       valid = true;
175       break;
176     default:
177       tag->fTypeString = kPDFStructureTypeNonStruct;
178   }
179 
180   if (ui::IsCellOrTableHeader(ax_node->data().role)) {
181     base::Optional<int> row_span = ax_node->GetTableCellRowSpan();
182     if (row_span.has_value()) {
183       tag->fAttributes.appendInt(kPDFTableAttributeOwner,
184                                  kPDFTableCellRowSpanAttribute,
185                                  row_span.value());
186     }
187     base::Optional<int> col_span = ax_node->GetTableCellColSpan();
188     if (col_span.has_value()) {
189       tag->fAttributes.appendInt(kPDFTableAttributeOwner,
190                                  kPDFTableCellColSpanAttribute,
191                                  col_span.value());
192     }
193   }
194 
195   std::string lang = ax_node->GetLanguage();
196   std::string parent_lang =
197       ax_node->parent() ? ax_node->parent()->GetLanguage() : "";
198   if (!lang.empty() && lang != parent_lang)
199     tag->fLang = lang.c_str();
200 
201   size_t children_count = ax_node->GetUnignoredChildCount();
202   tag->fChildVector.resize(children_count);
203   for (size_t i = 0; i < children_count; i++) {
204     tag->fChildVector[i] = std::make_unique<SkPDF::StructureElementNode>();
205     bool success = RecursiveBuildStructureTree(
206         ax_node->GetUnignoredChildAtIndex(i), tag->fChildVector[i].get());
207 
208     if (success)
209       valid = true;
210   }
211 
212   return valid;
213 #else  // BUILDFLAG(ENABLE_TAGGED_PDF)
214   return false;
215 #endif
216 }
217 
218 }  // namespace
219 
220 namespace printing {
221 
MakePdfDocument(const std::string & creator,const ui::AXTreeUpdate & accessibility_tree,SkWStream * stream)222 sk_sp<SkDocument> MakePdfDocument(const std::string& creator,
223                                   const ui::AXTreeUpdate& accessibility_tree,
224                                   SkWStream* stream) {
225   SkPDF::Metadata metadata;
226   SkTime::DateTime now = TimeToSkTime(base::Time::Now());
227   metadata.fCreation = now;
228   metadata.fModified = now;
229   metadata.fCreator = creator.empty()
230                           ? SkString("Chromium")
231                           : SkString(creator.c_str(), creator.size());
232   metadata.fRasterDPI = 300.0f;
233 
234   SkPDF::StructureElementNode tag_root = {};
235   if (!accessibility_tree.nodes.empty()) {
236     ui::AXTree tree(accessibility_tree);
237     if (RecursiveBuildStructureTree(tree.root(), &tag_root))
238       metadata.fStructureElementTreeRoot = &tag_root;
239   }
240 
241   return SkPDF::MakeDocument(stream, metadata);
242 }
243 
SerializeOopPicture(SkPicture * pic,void * ctx)244 sk_sp<SkData> SerializeOopPicture(SkPicture* pic, void* ctx) {
245   const auto* context = reinterpret_cast<const ContentToProxyTokenMap*>(ctx);
246   uint32_t pic_id = pic->uniqueID();
247   auto iter = context->find(pic_id);
248   if (iter == context->end())
249     return nullptr;
250 
251   return SkData::MakeWithCopy(&pic_id, sizeof(pic_id));
252 }
253 
DeserializeOopPicture(const void * data,size_t length,void * ctx)254 sk_sp<SkPicture> DeserializeOopPicture(const void* data,
255                                        size_t length,
256                                        void* ctx) {
257   uint32_t pic_id;
258   if (length < sizeof(pic_id)) {
259     NOTREACHED();  // Should not happen if the content is as written.
260     return GetEmptyPicture();
261   }
262   memcpy(&pic_id, data, sizeof(pic_id));
263 
264   auto* context = reinterpret_cast<PictureDeserializationContext*>(ctx);
265   auto iter = context->find(pic_id);
266   if (iter == context->end() || !iter->second) {
267     // When we don't have the out-of-process picture available, we return
268     // an empty picture. Returning a nullptr will cause the deserialization
269     // crash.
270     return GetEmptyPicture();
271   }
272   return iter->second;
273 }
274 
SerializeOopTypeface(SkTypeface * typeface,void * ctx)275 sk_sp<SkData> SerializeOopTypeface(SkTypeface* typeface, void* ctx) {
276   auto* context = reinterpret_cast<TypefaceSerializationContext*>(ctx);
277   SkFontID typeface_id = typeface->uniqueID();
278   bool data_included = context->insert(typeface_id).second;
279 
280   // Need the typeface ID to identify the desired typeface.  Include an
281   // indicator for when typeface data actually follows vs. when the typeface
282   // should already exist in a cache when deserializing.
283   SkDynamicMemoryWStream stream;
284   stream.write32(typeface_id);
285   stream.writeBool(data_included);
286   if (data_included) {
287     typeface->serialize(&stream, SkTypeface::SerializeBehavior::kDoIncludeData);
288   }
289   return stream.detachAsData();
290 }
291 
DeserializeOopTypeface(const void * data,size_t length,void * ctx)292 sk_sp<SkTypeface> DeserializeOopTypeface(const void* data,
293                                          size_t length,
294                                          void* ctx) {
295   SkStream* stream = *(reinterpret_cast<SkStream**>(const_cast<void*>(data)));
296   if (length < sizeof(stream)) {
297     NOTREACHED();  // Should not happen if the content is as written.
298     return nullptr;
299   }
300 
301   SkFontID id;
302   if (!stream->readU32(&id)) {
303     return nullptr;
304   }
305   bool data_included;
306   if (!stream->readBool(&data_included)) {
307     return nullptr;
308   }
309 
310   auto* context = reinterpret_cast<TypefaceDeserializationContext*>(ctx);
311   auto iter = context->find(id);
312   if (iter != context->end()) {
313     DCHECK(!data_included);
314     return iter->second;
315   }
316 
317   // Typeface not encountered before, expect it to be present in the stream.
318   DCHECK(data_included);
319   sk_sp<SkTypeface> typeface = SkTypeface::MakeDeserialize(stream);
320   context->emplace(id, typeface);
321   return typeface;
322 }
323 
SerializationProcs(PictureSerializationContext * picture_ctx,TypefaceSerializationContext * typeface_ctx)324 SkSerialProcs SerializationProcs(PictureSerializationContext* picture_ctx,
325                                  TypefaceSerializationContext* typeface_ctx) {
326   SkSerialProcs procs;
327   procs.fPictureProc = SerializeOopPicture;
328   procs.fPictureCtx = picture_ctx;
329   procs.fTypefaceProc = SerializeOopTypeface;
330   procs.fTypefaceCtx = typeface_ctx;
331   return procs;
332 }
333 
DeserializationProcs(PictureDeserializationContext * picture_ctx,TypefaceDeserializationContext * typeface_ctx)334 SkDeserialProcs DeserializationProcs(
335     PictureDeserializationContext* picture_ctx,
336     TypefaceDeserializationContext* typeface_ctx) {
337   SkDeserialProcs procs;
338   procs.fPictureProc = DeserializeOopPicture;
339   procs.fPictureCtx = picture_ctx;
340   procs.fTypefaceProc = DeserializeOopTypeface;
341   procs.fTypefaceCtx = typeface_ctx;
342   return procs;
343 }
344 
345 }  // namespace printing
346