• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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