1 // Copyright 2016 The PDFium Authors
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 <limits.h>
6
7 #include <iterator>
8 #include <optional>
9
10 #include "core/fxcrt/stl_util.h"
11 #include "public/fpdf_structtree.h"
12 #include "testing/embedder_test.h"
13 #include "testing/fx_string_testhelpers.h"
14
15 class FPDFStructTreeEmbedderTest : public EmbedderTest {};
16
TEST_F(FPDFStructTreeEmbedderTest,GetAltText)17 TEST_F(FPDFStructTreeEmbedderTest, GetAltText) {
18 ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf"));
19 ScopedEmbedderTestPage page = LoadScopedPage(0);
20 ASSERT_TRUE(page);
21
22 {
23 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
24 ASSERT_TRUE(struct_tree);
25 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
26
27 FPDF_STRUCTELEMENT element =
28 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), -1);
29 EXPECT_FALSE(element);
30 element = FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 1);
31 EXPECT_FALSE(element);
32 element = FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
33 ASSERT_TRUE(element);
34 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(element));
35 EXPECT_EQ(0U, FPDF_StructElement_GetAltText(element, nullptr, 0));
36
37 ASSERT_EQ(1, FPDF_StructElement_CountChildren(element));
38 FPDF_STRUCTELEMENT child_element =
39 FPDF_StructElement_GetChildAtIndex(element, -1);
40 EXPECT_FALSE(child_element);
41 child_element = FPDF_StructElement_GetChildAtIndex(element, 1);
42 EXPECT_FALSE(child_element);
43 child_element = FPDF_StructElement_GetChildAtIndex(element, 0);
44 ASSERT_TRUE(child_element);
45 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(child_element));
46 EXPECT_EQ(0U, FPDF_StructElement_GetAltText(child_element, nullptr, 0));
47
48 ASSERT_EQ(1, FPDF_StructElement_CountChildren(child_element));
49 FPDF_STRUCTELEMENT gchild_element =
50 FPDF_StructElement_GetChildAtIndex(child_element, -1);
51 EXPECT_FALSE(gchild_element);
52 gchild_element = FPDF_StructElement_GetChildAtIndex(child_element, 1);
53 EXPECT_FALSE(gchild_element);
54 gchild_element = FPDF_StructElement_GetChildAtIndex(child_element, 0);
55 ASSERT_TRUE(gchild_element);
56 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(gchild_element));
57 ASSERT_EQ(24U, FPDF_StructElement_GetAltText(gchild_element, nullptr, 0));
58
59 unsigned short buffer[12] = {};
60 // Deliberately pass in a small buffer size to make sure |buffer| remains
61 // untouched.
62 ASSERT_EQ(24U, FPDF_StructElement_GetAltText(gchild_element, buffer, 1));
63 for (unsigned short b : buffer) {
64 EXPECT_EQ(0U, b);
65 }
66 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(gchild_element));
67 ASSERT_EQ(24U, FPDF_StructElement_GetAltText(gchild_element, buffer,
68 sizeof(buffer)));
69 EXPECT_EQ(L"Black Image", GetPlatformWString(buffer));
70
71 ASSERT_EQ(1, FPDF_StructElement_CountChildren(gchild_element));
72 FPDF_STRUCTELEMENT ggchild_element =
73 FPDF_StructElement_GetChildAtIndex(gchild_element, 0);
74 EXPECT_FALSE(ggchild_element);
75 }
76 }
77
TEST_F(FPDFStructTreeEmbedderTest,GetActualText)78 TEST_F(FPDFStructTreeEmbedderTest, GetActualText) {
79 ASSERT_TRUE(OpenDocument("tagged_actual_text.pdf"));
80 ScopedEmbedderTestPage page = LoadScopedPage(0);
81 ASSERT_TRUE(page);
82
83 {
84 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
85 ASSERT_TRUE(struct_tree);
86 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
87
88 EXPECT_EQ(0U, FPDF_StructElement_GetActualText(nullptr, nullptr, 0));
89
90 FPDF_STRUCTELEMENT element =
91 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
92 ASSERT_TRUE(element);
93 EXPECT_EQ(0U, FPDF_StructElement_GetActualText(element, nullptr, 0));
94
95 ASSERT_EQ(1, FPDF_StructElement_CountChildren(element));
96 FPDF_STRUCTELEMENT child_element =
97 FPDF_StructElement_GetChildAtIndex(element, 0);
98 ASSERT_TRUE(child_element);
99 EXPECT_EQ(0U, FPDF_StructElement_GetActualText(child_element, nullptr, 0));
100
101 ASSERT_EQ(1, FPDF_StructElement_CountChildren(child_element));
102 FPDF_STRUCTELEMENT gchild_element =
103 FPDF_StructElement_GetChildAtIndex(child_element, 0);
104 ASSERT_TRUE(gchild_element);
105 ASSERT_EQ(24U,
106 FPDF_StructElement_GetActualText(gchild_element, nullptr, 0));
107
108 unsigned short buffer[12] = {};
109 // Deliberately pass in a small buffer size to make sure |buffer| remains
110 // untouched.
111 ASSERT_EQ(24U, FPDF_StructElement_GetActualText(gchild_element, buffer, 1));
112 for (unsigned short b : buffer) {
113 EXPECT_EQ(0U, b);
114 }
115 ASSERT_EQ(24U, FPDF_StructElement_GetActualText(gchild_element, buffer,
116 sizeof(buffer)));
117 EXPECT_EQ(L"Actual Text", GetPlatformWString(buffer));
118 }
119 }
120
TEST_F(FPDFStructTreeEmbedderTest,GetStringAttribute)121 TEST_F(FPDFStructTreeEmbedderTest, GetStringAttribute) {
122 ASSERT_TRUE(OpenDocument("tagged_table.pdf"));
123 ScopedEmbedderTestPage page = LoadScopedPage(0);
124 ASSERT_TRUE(page);
125
126 {
127 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
128 ASSERT_TRUE(struct_tree);
129 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
130
131 FPDF_STRUCTELEMENT document =
132 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
133 ASSERT_TRUE(document);
134
135 constexpr int kBufLen = 100;
136 uint16_t buffer[kBufLen] = {0};
137 EXPECT_EQ(18U, FPDF_StructElement_GetType(document, buffer, kBufLen));
138 EXPECT_EQ("Document", GetPlatformString(buffer));
139
140 ASSERT_EQ(1, FPDF_StructElement_CountChildren(document));
141 FPDF_STRUCTELEMENT table = FPDF_StructElement_GetChildAtIndex(document, 0);
142 ASSERT_TRUE(table);
143
144 EXPECT_EQ(12U, FPDF_StructElement_GetType(table, buffer, kBufLen));
145 EXPECT_EQ("Table", GetPlatformString(buffer));
146
147 // The table should have an attribute "Summary" set to the empty string.
148 EXPECT_EQ(2U, FPDF_StructElement_GetStringAttribute(table, "Summary",
149 buffer, kBufLen));
150
151 ASSERT_EQ(2, FPDF_StructElement_CountChildren(table));
152 FPDF_STRUCTELEMENT row = FPDF_StructElement_GetChildAtIndex(table, 0);
153 ASSERT_TRUE(row);
154
155 ASSERT_EQ(2, FPDF_StructElement_CountChildren(row));
156 FPDF_STRUCTELEMENT header_cell = FPDF_StructElement_GetChildAtIndex(row, 0);
157 ASSERT_TRUE(header_cell);
158
159 EXPECT_EQ(6U, FPDF_StructElement_GetType(header_cell, buffer, kBufLen));
160 EXPECT_EQ("TH", GetPlatformString(buffer));
161
162 // The header should have an attribute "Scope" with a scope of "Row".
163 EXPECT_EQ(8U, FPDF_StructElement_GetStringAttribute(header_cell, "Scope",
164 buffer, kBufLen));
165 EXPECT_EQ("Row", GetPlatformString(buffer));
166
167 // The header has an attribute "ColSpan", but it's not a string so it
168 // returns null.
169 EXPECT_EQ(0U, FPDF_StructElement_GetStringAttribute(header_cell, "ColSpan",
170 buffer, kBufLen));
171
172 // An unsupported attribute should return 0.
173 EXPECT_EQ(0U, FPDF_StructElement_GetStringAttribute(header_cell, "Other",
174 buffer, kBufLen));
175
176 // A null struct element should not crash.
177 EXPECT_EQ(0U, FPDF_StructElement_GetStringAttribute(nullptr, "Other",
178 buffer, kBufLen));
179 }
180 }
181
TEST_F(FPDFStructTreeEmbedderTest,GetStringAttributeBadStructElement)182 TEST_F(FPDFStructTreeEmbedderTest, GetStringAttributeBadStructElement) {
183 ASSERT_TRUE(OpenDocument("tagged_table_bad_elem.pdf"));
184 ScopedEmbedderTestPage page = LoadScopedPage(0);
185 ASSERT_TRUE(page);
186
187 {
188 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
189 ASSERT_TRUE(struct_tree);
190 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
191
192 FPDF_STRUCTELEMENT document =
193 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
194 ASSERT_TRUE(document);
195
196 constexpr int kBufLen = 100;
197 uint16_t buffer[kBufLen] = {0};
198 EXPECT_EQ(18U, FPDF_StructElement_GetType(document, buffer, kBufLen));
199 EXPECT_EQ("Document", GetPlatformString(buffer));
200
201 // The table can be retrieved, even though it does not have /Type.
202 ASSERT_EQ(1, FPDF_StructElement_CountChildren(document));
203 FPDF_STRUCTELEMENT table = FPDF_StructElement_GetChildAtIndex(document, 0);
204 ASSERT_TRUE(table);
205
206 EXPECT_EQ(12U, FPDF_StructElement_GetType(table, buffer, kBufLen));
207 EXPECT_EQ("Table", GetPlatformString(buffer));
208
209 // The table entry cannot be retrieved, as the element is malformed.
210 EXPECT_EQ(0U, FPDF_StructElement_GetStringAttribute(table, "Summary",
211 buffer, kBufLen));
212
213 // The row can be retrieved, even though it had an invalid /Type.
214 ASSERT_EQ(1, FPDF_StructElement_CountChildren(table));
215 FPDF_STRUCTELEMENT row = FPDF_StructElement_GetChildAtIndex(table, 0);
216 EXPECT_TRUE(row);
217 }
218 }
219
TEST_F(FPDFStructTreeEmbedderTest,GetID)220 TEST_F(FPDFStructTreeEmbedderTest, GetID) {
221 ASSERT_TRUE(OpenDocument("tagged_table.pdf"));
222 ScopedEmbedderTestPage page = LoadScopedPage(0);
223 ASSERT_TRUE(page);
224
225 {
226 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
227 ASSERT_TRUE(struct_tree);
228 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
229
230 FPDF_STRUCTELEMENT document =
231 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
232 ASSERT_TRUE(document);
233
234 constexpr int kBufLen = 100;
235 uint16_t buffer[kBufLen] = {0};
236 EXPECT_EQ(18U, FPDF_StructElement_GetType(document, buffer, kBufLen));
237 EXPECT_EQ("Document", GetPlatformString(buffer));
238
239 // The document has no ID.
240 EXPECT_EQ(0U, FPDF_StructElement_GetID(document, buffer, kBufLen));
241
242 ASSERT_EQ(1, FPDF_StructElement_CountChildren(document));
243 FPDF_STRUCTELEMENT table = FPDF_StructElement_GetChildAtIndex(document, 0);
244 ASSERT_TRUE(table);
245
246 EXPECT_EQ(12U, FPDF_StructElement_GetType(table, buffer, kBufLen));
247 EXPECT_EQ("Table", GetPlatformString(buffer));
248
249 // The table has an ID.
250 EXPECT_EQ(14U, FPDF_StructElement_GetID(table, buffer, kBufLen));
251 EXPECT_EQ("node12", GetPlatformString(buffer));
252
253 // The first child of the table is a row, which has an empty ID.
254 // It returns 2U, the length of an empty string, instead of 0U,
255 // representing null.
256 ASSERT_EQ(2, FPDF_StructElement_CountChildren(table));
257 FPDF_STRUCTELEMENT row = FPDF_StructElement_GetChildAtIndex(table, 0);
258 ASSERT_TRUE(row);
259 EXPECT_EQ(2U, FPDF_StructElement_GetID(row, buffer, kBufLen));
260 }
261 }
262
TEST_F(FPDFStructTreeEmbedderTest,GetLang)263 TEST_F(FPDFStructTreeEmbedderTest, GetLang) {
264 ASSERT_TRUE(OpenDocument("tagged_table.pdf"));
265 ScopedEmbedderTestPage page = LoadScopedPage(0);
266 ASSERT_TRUE(page);
267
268 {
269 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
270 ASSERT_TRUE(struct_tree);
271 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
272
273 FPDF_STRUCTELEMENT document =
274 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
275 ASSERT_TRUE(document);
276
277 constexpr int kBufLen = 100;
278 uint16_t buffer[kBufLen] = {0};
279 EXPECT_EQ(18U, FPDF_StructElement_GetType(document, buffer, kBufLen));
280 EXPECT_EQ("Document", GetPlatformString(buffer));
281
282 // Nullptr test
283 EXPECT_EQ(0U, FPDF_StructElement_GetLang(nullptr, buffer, kBufLen));
284
285 // The document has a language.
286 EXPECT_EQ(12U, FPDF_StructElement_GetLang(document, buffer, kBufLen));
287 EXPECT_EQ("en-US", GetPlatformString(buffer));
288
289 ASSERT_EQ(1, FPDF_StructElement_CountChildren(document));
290 FPDF_STRUCTELEMENT table = FPDF_StructElement_GetChildAtIndex(document, 0);
291 ASSERT_TRUE(table);
292
293 // The first child is a table, with a language.
294 EXPECT_EQ(12U, FPDF_StructElement_GetType(table, buffer, kBufLen));
295 EXPECT_EQ("Table", GetPlatformString(buffer));
296
297 EXPECT_EQ(6U, FPDF_StructElement_GetLang(table, buffer, kBufLen));
298 EXPECT_EQ("hu", GetPlatformString(buffer));
299
300 // The first child of the table is a row, which doesn't have a
301 // language explicitly set on it.
302 ASSERT_EQ(2, FPDF_StructElement_CountChildren(table));
303 FPDF_STRUCTELEMENT row = FPDF_StructElement_GetChildAtIndex(table, 0);
304 ASSERT_TRUE(row);
305 EXPECT_EQ(0U, FPDF_StructElement_GetLang(row, buffer, kBufLen));
306 }
307 }
308
309 // See also FPDFEditEmbedderTest.TraverseMarkedContentID, which traverses the
310 // marked contents using FPDFPageObj_GetMark() and related API.
TEST_F(FPDFStructTreeEmbedderTest,GetMarkedContentID)311 TEST_F(FPDFStructTreeEmbedderTest, GetMarkedContentID) {
312 ASSERT_TRUE(OpenDocument("marked_content_id.pdf"));
313 ScopedEmbedderTestPage page = LoadScopedPage(0);
314 ASSERT_TRUE(page);
315
316 {
317 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
318 ASSERT_TRUE(struct_tree);
319 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
320
321 FPDF_STRUCTELEMENT element =
322 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
323 EXPECT_EQ(0, FPDF_StructElement_GetMarkedContentID(element));
324 }
325 }
326
TEST_F(FPDFStructTreeEmbedderTest,GetMarkedContentIdAtIndex)327 TEST_F(FPDFStructTreeEmbedderTest, GetMarkedContentIdAtIndex) {
328 ASSERT_TRUE(OpenDocument("tagged_marked_content.pdf"));
329 ScopedEmbedderTestPage page = LoadScopedPage(0);
330 ASSERT_TRUE(page);
331
332 {
333 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
334 ASSERT_TRUE(struct_tree);
335 ASSERT_EQ(4, FPDF_StructTree_CountChildren(struct_tree.get()));
336
337 // K is an integer MCID
338 FPDF_STRUCTELEMENT child1 =
339 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
340 ASSERT_TRUE(child1);
341 // Legacy API
342 EXPECT_EQ(0, FPDF_StructElement_GetMarkedContentID(child1));
343
344 // K is a dict containing MCR object reference
345 FPDF_STRUCTELEMENT child2 =
346 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 1);
347 ASSERT_TRUE(child2);
348
349 // K is an array containing dict MCR object reference and integer MCID
350 FPDF_STRUCTELEMENT child3 =
351 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 2);
352 ASSERT_TRUE(child3);
353
354 // K does not exist
355 FPDF_STRUCTELEMENT child4 =
356 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 3);
357 ASSERT_TRUE(child4);
358
359 // New APIs
360 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentIdCount(nullptr));
361 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentIdAtIndex(nullptr, 0));
362 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentIdAtIndex(child1, -1));
363 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentIdAtIndex(child1, 1));
364 EXPECT_EQ(1, FPDF_StructElement_GetMarkedContentIdCount(child1));
365 EXPECT_EQ(0, FPDF_StructElement_GetMarkedContentIdAtIndex(child1, 0));
366
367 EXPECT_EQ(1, FPDF_StructElement_GetMarkedContentIdCount(child2));
368 EXPECT_EQ(1, FPDF_StructElement_GetMarkedContentIdAtIndex(child2, 0));
369
370 EXPECT_EQ(2, FPDF_StructElement_GetMarkedContentIdCount(child3));
371 EXPECT_EQ(2, FPDF_StructElement_GetMarkedContentIdAtIndex(child3, 0));
372 EXPECT_EQ(3, FPDF_StructElement_GetMarkedContentIdAtIndex(child3, 1));
373
374 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentIdCount(child4));
375 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentIdAtIndex(child4, 0));
376 }
377 }
378
TEST_F(FPDFStructTreeEmbedderTest,GetChildMarkedContentID)379 TEST_F(FPDFStructTreeEmbedderTest, GetChildMarkedContentID) {
380 ASSERT_TRUE(OpenDocument("tagged_mcr_multipage.pdf"));
381
382 // Using the loop to make difference clear
383 for (int page_i : {0, 1}) {
384 ScopedEmbedderTestPage page = LoadScopedPage(page_i);
385 ASSERT_TRUE(page);
386 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
387 ASSERT_TRUE(struct_tree);
388 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
389
390 FPDF_STRUCTELEMENT struct_doc =
391 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
392 ASSERT_TRUE(struct_doc);
393 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(struct_doc));
394
395 ASSERT_EQ(2, FPDF_StructElement_CountChildren(struct_doc));
396 FPDF_STRUCTELEMENT child1 =
397 FPDF_StructElement_GetChildAtIndex(struct_doc, 0);
398 EXPECT_FALSE(child1);
399 FPDF_STRUCTELEMENT child2 =
400 FPDF_StructElement_GetChildAtIndex(struct_doc, 1);
401 EXPECT_FALSE(child2);
402
403 EXPECT_EQ(2, FPDF_StructElement_GetMarkedContentIdCount(struct_doc));
404
405 // Both MCID are returned as if part of this page, while they are not.
406 // So `FPDF_StructElement_GetMarkedContentIdAtIndex(...)` does not work
407 // for StructElement spanning multiple pages.
408 EXPECT_EQ(0, FPDF_StructElement_GetMarkedContentIdAtIndex(struct_doc, 0));
409 EXPECT_EQ(0, FPDF_StructElement_GetMarkedContentIdAtIndex(struct_doc, 1));
410
411 // One MCR is pointing to page 1, another to page2, so those are different
412 // for different pages.
413 EXPECT_EQ(page_i == 0 ? 0 : -1,
414 FPDF_StructElement_GetChildMarkedContentID(struct_doc, 0));
415 EXPECT_EQ(page_i == 1 ? 0 : -1,
416 FPDF_StructElement_GetChildMarkedContentID(struct_doc, 1));
417 // Invalid index
418 EXPECT_EQ(-1, FPDF_StructElement_GetChildMarkedContentID(struct_doc, -1));
419 EXPECT_EQ(-1, FPDF_StructElement_GetChildMarkedContentID(struct_doc, 2));
420 // Invalid element
421 EXPECT_EQ(-1, FPDF_StructElement_GetChildMarkedContentID(nullptr, 0));
422 }
423 }
424
TEST_F(FPDFStructTreeEmbedderTest,GetType)425 TEST_F(FPDFStructTreeEmbedderTest, GetType) {
426 ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf"));
427 ScopedEmbedderTestPage page = LoadScopedPage(0);
428 ASSERT_TRUE(page);
429
430 {
431 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
432 ASSERT_TRUE(struct_tree);
433 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
434
435 FPDF_STRUCTELEMENT element =
436 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
437 ASSERT_TRUE(element);
438
439 // test nullptr inputs
440 unsigned short buffer[12] = {};
441 ASSERT_EQ(0U, FPDF_StructElement_GetType(nullptr, buffer, sizeof(buffer)));
442 ASSERT_EQ(0U, FPDF_StructElement_GetType(nullptr, nullptr, 0));
443 ASSERT_EQ(18U, FPDF_StructElement_GetType(element, nullptr, 0));
444
445 // Deliberately pass in a small buffer size to make sure |buffer| remains
446 // untouched.
447 fxcrt::Fill(buffer, 0xbdfcu);
448 ASSERT_EQ(18U, FPDF_StructElement_GetType(element, buffer, 1));
449 for (const auto b : buffer) {
450 EXPECT_EQ(0xbdfcu, b);
451 }
452 ASSERT_EQ(18U, FPDF_StructElement_GetType(element, buffer, sizeof(buffer)));
453 EXPECT_EQ(L"Document", GetPlatformWString(buffer));
454 }
455 }
456
TEST_F(FPDFStructTreeEmbedderTest,GetObjType)457 TEST_F(FPDFStructTreeEmbedderTest, GetObjType) {
458 ASSERT_TRUE(OpenDocument("tagged_table_bad_elem.pdf"));
459 ScopedEmbedderTestPage page = LoadScopedPage(0);
460 ASSERT_TRUE(page);
461
462 {
463 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
464 ASSERT_TRUE(struct_tree);
465 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
466
467 FPDF_STRUCTELEMENT child =
468 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
469 ASSERT_TRUE(child);
470
471 // test nullptr inputs
472 unsigned short buffer[28] = {};
473 ASSERT_EQ(0U,
474 FPDF_StructElement_GetObjType(nullptr, buffer, sizeof(buffer)));
475 ASSERT_EQ(0U, FPDF_StructElement_GetObjType(nullptr, nullptr, 0));
476 ASSERT_EQ(22U, FPDF_StructElement_GetObjType(child, nullptr, 0));
477
478 // Deliberately pass in a small buffer size to make sure `buffer` remains
479 // untouched.
480 ASSERT_EQ(22U, FPDF_StructElement_GetObjType(child, buffer, 1));
481 for (unsigned short b : buffer) {
482 EXPECT_EQ(0U, b);
483 }
484 ASSERT_EQ(22U,
485 FPDF_StructElement_GetObjType(child, buffer, sizeof(buffer)));
486 EXPECT_EQ(L"StructElem", GetPlatformWString(buffer));
487
488 ASSERT_EQ(1, FPDF_StructElement_CountChildren(child));
489 FPDF_STRUCTELEMENT gchild = FPDF_StructElement_GetChildAtIndex(child, 0);
490
491 fxcrt::Fill(buffer, 0xbdfcu);
492 // Missing /Type in `gchild`
493 ASSERT_EQ(0U,
494 FPDF_StructElement_GetObjType(gchild, buffer, sizeof(buffer)));
495 // Buffer is untouched.
496 for (const auto b : buffer) {
497 EXPECT_EQ(0xbdfcu, b);
498 }
499 ASSERT_EQ(1, FPDF_StructElement_CountChildren(gchild));
500 FPDF_STRUCTELEMENT ggchild = FPDF_StructElement_GetChildAtIndex(gchild, 0);
501 ASSERT_EQ(28U,
502 FPDF_StructElement_GetObjType(ggchild, buffer, sizeof(buffer)));
503 // Reading bad elem also works.
504 EXPECT_EQ(L"NotStructElem", GetPlatformWString(buffer));
505 }
506 }
507
TEST_F(FPDFStructTreeEmbedderTest,GetParent)508 TEST_F(FPDFStructTreeEmbedderTest, GetParent) {
509 ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf"));
510 ScopedEmbedderTestPage page = LoadScopedPage(0);
511 ASSERT_TRUE(page);
512
513 {
514 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
515 ASSERT_TRUE(struct_tree);
516 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
517
518 FPDF_STRUCTELEMENT parent =
519 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
520 ASSERT_TRUE(parent);
521
522 ASSERT_EQ(1, FPDF_StructElement_CountChildren(parent));
523
524 FPDF_STRUCTELEMENT child = FPDF_StructElement_GetChildAtIndex(parent, 0);
525 ASSERT_TRUE(child);
526
527 // test nullptr inputs
528 ASSERT_EQ(nullptr, FPDF_StructElement_GetParent(nullptr));
529
530 ASSERT_EQ(parent, FPDF_StructElement_GetParent(child));
531
532 // The parent of `parent` is StructTreeRoot and no longer a StructElement.
533 // We currently handle this case by returning a nullptr.
534 ASSERT_EQ(nullptr, FPDF_StructElement_GetParent(parent));
535 }
536 }
537
TEST_F(FPDFStructTreeEmbedderTest,GetTitle)538 TEST_F(FPDFStructTreeEmbedderTest, GetTitle) {
539 ASSERT_TRUE(OpenDocument("tagged_alt_text.pdf"));
540 ScopedEmbedderTestPage page = LoadScopedPage(0);
541 ASSERT_TRUE(page);
542
543 {
544 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
545 ASSERT_TRUE(struct_tree);
546 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
547
548 FPDF_STRUCTELEMENT element =
549 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
550 ASSERT_TRUE(element);
551
552 // test nullptr inputs
553 unsigned short buffer[13] = {};
554 ASSERT_EQ(0U, FPDF_StructElement_GetTitle(nullptr, buffer, sizeof(buffer)));
555 ASSERT_EQ(0U, FPDF_StructElement_GetTitle(nullptr, nullptr, 0));
556 ASSERT_EQ(20U, FPDF_StructElement_GetTitle(element, nullptr, 0));
557
558 // Deliberately pass in a small buffer size to make sure |buffer| remains
559 // untouched.
560 fxcrt::Fill(buffer, 0xbdfcu);
561 ASSERT_EQ(20U, FPDF_StructElement_GetTitle(element, buffer, 1));
562 for (const auto b : buffer) {
563 EXPECT_EQ(0xbdfcu, b);
564 }
565
566 ASSERT_EQ(20U,
567 FPDF_StructElement_GetTitle(element, buffer, sizeof(buffer)));
568 EXPECT_EQ(L"TitleText", GetPlatformWString(buffer));
569
570 ASSERT_EQ(1, FPDF_StructElement_CountChildren(element));
571 FPDF_STRUCTELEMENT child_element =
572 FPDF_StructElement_GetChildAtIndex(element, 0);
573 ASSERT_TRUE(element);
574
575 ASSERT_EQ(26U, FPDF_StructElement_GetTitle(child_element, buffer,
576 sizeof(buffer)));
577 EXPECT_EQ(L"symbol: 100k", GetPlatformWString(buffer));
578 }
579 }
580
TEST_F(FPDFStructTreeEmbedderTest,GetAttributes)581 TEST_F(FPDFStructTreeEmbedderTest, GetAttributes) {
582 ASSERT_TRUE(OpenDocument("tagged_table.pdf"));
583 ScopedEmbedderTestPage page = LoadScopedPage(0);
584 ASSERT_TRUE(page);
585
586 {
587 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
588 ASSERT_TRUE(struct_tree);
589 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
590
591 FPDF_STRUCTELEMENT document =
592 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
593 ASSERT_TRUE(document);
594
595 ASSERT_EQ(1, FPDF_StructElement_CountChildren(document));
596 ASSERT_EQ(-1, FPDF_StructElement_GetAttributeCount(document));
597 FPDF_STRUCTELEMENT table = FPDF_StructElement_GetChildAtIndex(document, 0);
598 ASSERT_TRUE(table);
599
600 ASSERT_EQ(2, FPDF_StructElement_CountChildren(table));
601
602 {
603 FPDF_STRUCTELEMENT tr = FPDF_StructElement_GetChildAtIndex(table, 0);
604 ASSERT_TRUE(tr);
605
606 ASSERT_EQ(2, FPDF_StructElement_CountChildren(tr));
607 FPDF_STRUCTELEMENT th = FPDF_StructElement_GetChildAtIndex(tr, 0);
608 ASSERT_TRUE(th);
609
610 ASSERT_EQ(2, FPDF_StructElement_GetAttributeCount(th));
611
612 // nullptr test
613 ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(document, 0));
614 ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(document, -1));
615 ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(th, 2));
616
617 FPDF_STRUCTELEMENT_ATTR attr =
618 FPDF_StructElement_GetAttributeAtIndex(th, 1);
619 ASSERT_TRUE(attr);
620
621 ASSERT_EQ(2, FPDF_StructElement_Attr_GetCount(attr));
622 ASSERT_FALSE(
623 FPDF_StructElement_Attr_GetName(attr, 1, nullptr, 0U, nullptr));
624 unsigned long buffer_len_needed = ULONG_MAX;
625 // Pass buffer = nullptr to obtain the size of the buffer needed,
626 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 1, nullptr, 0,
627 &buffer_len_needed));
628 EXPECT_EQ(2U, buffer_len_needed);
629 char buffer[8] = {};
630 unsigned long out_len = ULONG_MAX;
631 // Deliberately pass in a small buffer size to make sure `buffer` remains
632 // untouched.
633 ASSERT_TRUE(
634 FPDF_StructElement_Attr_GetName(attr, 1, buffer, 1, &out_len));
635 EXPECT_EQ(2U, out_len);
636 for (unsigned short b : buffer) {
637 EXPECT_EQ(0U, b);
638 }
639 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 1, buffer,
640 sizeof(buffer), &out_len));
641 EXPECT_EQ(2U, out_len);
642 EXPECT_STREQ("O", buffer);
643
644 // Make sure bad inputs do not work.
645 EXPECT_FALSE(FPDF_StructElement_Attr_GetValue(nullptr, ""));
646 EXPECT_FALSE(FPDF_StructElement_Attr_GetValue(attr, "DOES_NOT_EXIST"));
647 EXPECT_FALSE(FPDF_StructElement_Attr_GetValue(attr, "DOES_NOT_EXIST"));
648
649 FPDF_STRUCTELEMENT_ATTR_VALUE attr_value =
650 FPDF_StructElement_Attr_GetValue(attr, buffer);
651 ASSERT_TRUE(attr_value);
652
653 EXPECT_EQ(FPDF_OBJECT_NAME, FPDF_StructElement_Attr_GetType(attr_value));
654
655 unsigned short str_val[12] = {};
656 ASSERT_TRUE(FPDF_StructElement_Attr_GetStringValue(
657 attr_value, str_val, sizeof(str_val), &out_len));
658 EXPECT_EQ(12U, out_len);
659 EXPECT_EQ(L"Table", GetPlatformWString(str_val));
660
661 fxcrt::Fill(buffer, 0u);
662 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 0, buffer,
663 sizeof(buffer), &out_len));
664 EXPECT_EQ(8U, out_len);
665 EXPECT_STREQ("ColSpan", buffer);
666 attr_value = FPDF_StructElement_Attr_GetValue(attr, buffer);
667 ASSERT_TRUE(attr_value);
668 EXPECT_EQ(FPDF_OBJECT_NUMBER,
669 FPDF_StructElement_Attr_GetType(attr_value));
670 float num_val;
671 ASSERT_TRUE(FPDF_StructElement_Attr_GetNumberValue(attr_value, &num_val));
672 EXPECT_FLOAT_EQ(2.0f, num_val);
673 }
674
675 {
676 FPDF_STRUCTELEMENT tr = FPDF_StructElement_GetChildAtIndex(table, 1);
677 ASSERT_TRUE(tr);
678
679 ASSERT_EQ(1, FPDF_StructElement_GetAttributeCount(tr));
680 // nullptr when index out of range
681 ASSERT_EQ(nullptr, FPDF_StructElement_GetAttributeAtIndex(tr, 1));
682
683 ASSERT_EQ(2, FPDF_StructElement_CountChildren(tr));
684 FPDF_STRUCTELEMENT td = FPDF_StructElement_GetChildAtIndex(tr, 1);
685 ASSERT_TRUE(td);
686 {
687 // Test counting and obtaining attributes via reference
688 ASSERT_EQ(1, FPDF_StructElement_GetAttributeCount(td));
689 FPDF_STRUCTELEMENT_ATTR attr =
690 FPDF_StructElement_GetAttributeAtIndex(td, 0);
691 ASSERT_TRUE(attr);
692 ASSERT_EQ(4, FPDF_StructElement_Attr_GetCount(attr));
693 // Test string and blob type
694 {
695 char buffer[16] = {};
696 unsigned long out_len = ULONG_MAX;
697 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(
698 attr, 0, buffer, sizeof(buffer), &out_len));
699 EXPECT_EQ(8U, out_len);
700 EXPECT_STREQ("ColProp", buffer);
701
702 FPDF_STRUCTELEMENT_ATTR_VALUE attr_value =
703 FPDF_StructElement_Attr_GetValue(attr, buffer);
704 ASSERT_TRUE(attr_value);
705 EXPECT_EQ(FPDF_OBJECT_STRING,
706 FPDF_StructElement_Attr_GetType(attr_value));
707
708 unsigned short str_val[12] = {};
709 ASSERT_TRUE(FPDF_StructElement_Attr_GetStringValue(
710 attr_value, str_val, sizeof(str_val), &out_len));
711 EXPECT_EQ(8U, out_len);
712 EXPECT_EQ(L"Sum", GetPlatformWString(str_val));
713
714 char blob_val[3] = {};
715 ASSERT_TRUE(FPDF_StructElement_Attr_GetBlobValue(
716 attr_value, blob_val, sizeof(blob_val), &out_len));
717 EXPECT_EQ(3U, out_len);
718 EXPECT_EQ('S', blob_val[0]);
719 EXPECT_EQ('u', blob_val[1]);
720 EXPECT_EQ('m', blob_val[2]);
721 }
722
723 // Test boolean type
724 {
725 char buffer[16] = {};
726 unsigned long out_len = ULONG_MAX;
727 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(
728 attr, 1, buffer, sizeof(buffer), &out_len));
729 EXPECT_EQ(7U, out_len);
730 EXPECT_STREQ("CurUSD", buffer);
731
732 FPDF_STRUCTELEMENT_ATTR_VALUE attr_value =
733 FPDF_StructElement_Attr_GetValue(attr, buffer);
734 ASSERT_TRUE(attr_value);
735 EXPECT_EQ(FPDF_OBJECT_BOOLEAN,
736 FPDF_StructElement_Attr_GetType(attr_value));
737 FPDF_BOOL val;
738 ASSERT_TRUE(
739 FPDF_StructElement_Attr_GetBooleanValue(attr_value, &val));
740 EXPECT_TRUE(val);
741 }
742
743 // Test reference to number
744 {
745 char buffer[16] = {};
746 unsigned long out_len = ULONG_MAX;
747 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(
748 attr, 3, buffer, sizeof(buffer), &out_len));
749 EXPECT_EQ(8U, out_len);
750 EXPECT_STREQ("RowSpan", buffer);
751
752 FPDF_STRUCTELEMENT_ATTR_VALUE attr_value =
753 FPDF_StructElement_Attr_GetValue(attr, buffer);
754 ASSERT_TRUE(attr_value);
755 EXPECT_EQ(FPDF_OBJECT_NUMBER,
756 FPDF_StructElement_Attr_GetType(attr_value));
757 float val;
758 ASSERT_TRUE(FPDF_StructElement_Attr_GetNumberValue(attr_value, &val));
759 EXPECT_FLOAT_EQ(3, val);
760 }
761 }
762 }
763 }
764 }
765
TEST_F(FPDFStructTreeEmbedderTest,GetAttributesFromChildAttributes)766 TEST_F(FPDFStructTreeEmbedderTest, GetAttributesFromChildAttributes) {
767 ASSERT_TRUE(OpenDocument("tagged_actual_text.pdf"));
768 ScopedEmbedderTestPage page = LoadScopedPage(0);
769 ASSERT_TRUE(page);
770
771 {
772 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
773 ASSERT_TRUE(struct_tree);
774 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
775
776 FPDF_STRUCTELEMENT element =
777 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
778 ASSERT_TRUE(element);
779 ASSERT_EQ(1, FPDF_StructElement_CountChildren(element));
780
781 FPDF_STRUCTELEMENT child_element =
782 FPDF_StructElement_GetChildAtIndex(element, 0);
783 ASSERT_TRUE(child_element);
784 ASSERT_EQ(1, FPDF_StructElement_CountChildren(child_element));
785
786 FPDF_STRUCTELEMENT gchild_element =
787 FPDF_StructElement_GetChildAtIndex(child_element, 0);
788 ASSERT_TRUE(gchild_element);
789
790 int gchild_attr_count =
791 FPDF_StructElement_GetAttributeCount(gchild_element);
792 ASSERT_EQ(1, gchild_attr_count);
793
794 FPDF_STRUCTELEMENT_ATTR attr =
795 FPDF_StructElement_GetAttributeAtIndex(gchild_element, 0);
796 ASSERT_TRUE(attr);
797
798 int attr_count = FPDF_StructElement_Attr_GetCount(attr);
799 ASSERT_EQ(5, attr_count);
800
801 char name[20] = {};
802 unsigned long required_len;
803 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 1, name, sizeof(name),
804 &required_len));
805 EXPECT_EQ(7u, required_len);
806 EXPECT_STREQ("Height", name);
807
808 // Reject bad values for FPDF_StructElement_Attr_CountChildren().
809 EXPECT_EQ(-1, FPDF_StructElement_Attr_CountChildren(nullptr));
810 EXPECT_FALSE(FPDF_StructElement_Attr_GetChildAtIndex(nullptr, -1));
811 EXPECT_FALSE(FPDF_StructElement_Attr_GetChildAtIndex(nullptr, 0));
812 EXPECT_FALSE(FPDF_StructElement_Attr_GetChildAtIndex(nullptr, 1));
813 {
814 FPDF_STRUCTELEMENT_ATTR_VALUE attr_value =
815 FPDF_StructElement_Attr_GetValue(attr, name);
816 ASSERT_TRUE(attr_value);
817 EXPECT_EQ(FPDF_OBJECT_NUMBER,
818 FPDF_StructElement_Attr_GetType(attr_value));
819 EXPECT_EQ(-1, FPDF_StructElement_Attr_CountChildren(attr_value));
820 EXPECT_FALSE(FPDF_StructElement_Attr_GetChildAtIndex(attr_value, -1));
821 EXPECT_FALSE(FPDF_StructElement_Attr_GetChildAtIndex(attr_value, 0));
822 EXPECT_FALSE(FPDF_StructElement_Attr_GetChildAtIndex(attr_value, 1));
823 }
824
825 ASSERT_TRUE(FPDF_StructElement_Attr_GetName(attr, 0, name, sizeof(name),
826 &required_len));
827 EXPECT_EQ(5u, required_len);
828 EXPECT_STREQ("BBox", name);
829
830 FPDF_STRUCTELEMENT_ATTR_VALUE attr_value =
831 FPDF_StructElement_Attr_GetValue(attr, name);
832 ASSERT_TRUE(attr_value);
833 EXPECT_EQ(FPDF_OBJECT_ARRAY, FPDF_StructElement_Attr_GetType(attr_value));
834 EXPECT_EQ(4, FPDF_StructElement_Attr_CountChildren(attr_value));
835 FPDF_STRUCTELEMENT_ATTR_VALUE nested_attr_value0 =
836 FPDF_StructElement_Attr_GetChildAtIndex(attr_value, 0);
837 ASSERT_TRUE(nested_attr_value0);
838 EXPECT_EQ(FPDF_OBJECT_NUMBER,
839 FPDF_StructElement_Attr_GetType(nested_attr_value0));
840 FPDF_STRUCTELEMENT_ATTR_VALUE nested_attr_value3 =
841 FPDF_StructElement_Attr_GetChildAtIndex(attr_value, 3);
842 ASSERT_TRUE(nested_attr_value3);
843 EXPECT_EQ(FPDF_OBJECT_NUMBER,
844 FPDF_StructElement_Attr_GetType(nested_attr_value3));
845 }
846 }
847
TEST_F(FPDFStructTreeEmbedderTest,GetStructTreeForNestedTaggedPDF)848 TEST_F(FPDFStructTreeEmbedderTest, GetStructTreeForNestedTaggedPDF) {
849 ASSERT_TRUE(OpenDocument("tagged_nested.pdf"));
850 ScopedEmbedderTestPage page = LoadScopedPage(0);
851 ASSERT_TRUE(page);
852
853 {
854 // This call should not crash. https://crbug.com/pdfium/1480
855 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
856 ASSERT_TRUE(struct_tree);
857 }
858 }
859
TEST_F(FPDFStructTreeEmbedderTest,MarkedContentReferenceAndObjectReference)860 TEST_F(FPDFStructTreeEmbedderTest, MarkedContentReferenceAndObjectReference) {
861 ASSERT_TRUE(OpenDocument("tagged_mcr_objr.pdf"));
862 ScopedEmbedderTestPage page = LoadScopedPage(0);
863 ASSERT_TRUE(page);
864
865 {
866 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
867 ASSERT_TRUE(struct_tree);
868 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
869
870 FPDF_STRUCTELEMENT object8 =
871 FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0);
872 ASSERT_TRUE(object8);
873 unsigned short buffer[12];
874 ASSERT_EQ(18U, FPDF_StructElement_GetType(object8, buffer, sizeof(buffer)));
875 EXPECT_EQ(L"Document", GetPlatformWString(buffer));
876 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object8));
877 ASSERT_EQ(2, FPDF_StructElement_CountChildren(object8));
878
879 // First branch. 10 -> 12 -> 13 -> Inline dict.
880 FPDF_STRUCTELEMENT object10 =
881 FPDF_StructElement_GetChildAtIndex(object8, 0);
882 ASSERT_TRUE(object10);
883 ASSERT_EQ(20U,
884 FPDF_StructElement_GetType(object10, buffer, sizeof(buffer)));
885 EXPECT_EQ(L"NonStruct", GetPlatformWString(buffer));
886 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object10));
887 ASSERT_EQ(1, FPDF_StructElement_CountChildren(object10));
888
889 FPDF_STRUCTELEMENT object12 =
890 FPDF_StructElement_GetChildAtIndex(object10, 0);
891 ASSERT_TRUE(object12);
892 ASSERT_EQ(4U, FPDF_StructElement_GetType(object12, buffer, sizeof(buffer)));
893 EXPECT_EQ(L"P", GetPlatformWString(buffer));
894 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object12));
895 ASSERT_EQ(1, FPDF_StructElement_CountChildren(object12));
896
897 FPDF_STRUCTELEMENT object13 =
898 FPDF_StructElement_GetChildAtIndex(object12, 0);
899 ASSERT_TRUE(object13);
900 ASSERT_EQ(20U,
901 FPDF_StructElement_GetType(object13, buffer, sizeof(buffer)));
902 EXPECT_EQ(L"NonStruct", GetPlatformWString(buffer));
903 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object13));
904 ASSERT_EQ(1, FPDF_StructElement_CountChildren(object13));
905
906 // TODO(crbug.com/pdfium/672): Fetch this child element.
907 EXPECT_FALSE(FPDF_StructElement_GetChildAtIndex(object13, 0));
908
909 // Second branch. 11 -> 14 -> Inline dict.
910 // -> 15 -> Inline dict.
911 FPDF_STRUCTELEMENT object11 =
912 FPDF_StructElement_GetChildAtIndex(object8, 1);
913 ASSERT_TRUE(object11);
914 ASSERT_EQ(4U, FPDF_StructElement_GetType(object11, buffer, sizeof(buffer)));
915 EXPECT_EQ(L"P", GetPlatformWString(buffer));
916 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object11));
917 ASSERT_EQ(1, FPDF_StructElement_CountChildren(object11));
918
919 FPDF_STRUCTELEMENT object14 =
920 FPDF_StructElement_GetChildAtIndex(object11, 0);
921 ASSERT_TRUE(object14);
922 ASSERT_EQ(20U,
923 FPDF_StructElement_GetType(object14, buffer, sizeof(buffer)));
924 EXPECT_EQ(L"NonStruct", GetPlatformWString(buffer));
925 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object14));
926 ASSERT_EQ(2, FPDF_StructElement_CountChildren(object14));
927
928 // TODO(crbug.com/pdfium/672): Object 15 should be at index 1.
929 EXPECT_FALSE(FPDF_StructElement_GetChildAtIndex(object14, 1));
930 FPDF_STRUCTELEMENT object15 =
931 FPDF_StructElement_GetChildAtIndex(object14, 0);
932 ASSERT_TRUE(object15);
933 ASSERT_EQ(20U,
934 FPDF_StructElement_GetType(object15, buffer, sizeof(buffer)));
935 EXPECT_EQ(L"NonStruct", GetPlatformWString(buffer));
936 EXPECT_EQ(-1, FPDF_StructElement_GetMarkedContentID(object15));
937 ASSERT_EQ(1, FPDF_StructElement_CountChildren(object15));
938
939 // TODO(crbug.com/pdfium/672): Fetch this child element.
940 EXPECT_FALSE(FPDF_StructElement_GetChildAtIndex(object15, 0));
941 }
942 }
943
TEST_F(FPDFStructTreeEmbedderTest,Bug1768)944 TEST_F(FPDFStructTreeEmbedderTest, Bug1768) {
945 ASSERT_TRUE(OpenDocument("bug_1768.pdf"));
946 ScopedEmbedderTestPage page = LoadScopedPage(0);
947 ASSERT_TRUE(page);
948
949 {
950 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
951 ASSERT_TRUE(struct_tree);
952 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
953
954 // TODO(crbug.com/pdfium/1768): Fetch this child element. Then consider
955 // writing more of the test to make sure other elements in the tree can be
956 // fetched correctly as well.
957 EXPECT_FALSE(FPDF_StructTree_GetChildAtIndex(struct_tree.get(), 0));
958 }
959 }
960
TEST_F(FPDFStructTreeEmbedderTest,Bug1296920)961 TEST_F(FPDFStructTreeEmbedderTest, Bug1296920) {
962 ASSERT_TRUE(OpenDocument("bug_1296920.pdf"));
963 ScopedEmbedderTestPage page = LoadScopedPage(0);
964 ASSERT_TRUE(page);
965
966 {
967 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
968 ASSERT_TRUE(struct_tree);
969 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
970
971 // Destroying this tree should not crash.
972 }
973 }
974
TEST_F(FPDFStructTreeEmbedderTest,Bug1443100)975 TEST_F(FPDFStructTreeEmbedderTest, Bug1443100) {
976 ASSERT_TRUE(OpenDocument("tagged_table_bad_parent.pdf"));
977 ScopedEmbedderTestPage page = LoadScopedPage(0);
978 ASSERT_TRUE(page);
979
980 {
981 // Calling these APIs should not trigger a dangling pointer.
982 ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(page.get()));
983 ASSERT_TRUE(struct_tree);
984 ASSERT_EQ(1, FPDF_StructTree_CountChildren(struct_tree.get()));
985 }
986 }
987