/src/poppler/poppler/StructElement.cc
Line | Count | Source |
1 | | //======================================================================== |
2 | | // |
3 | | // StructElement.cc |
4 | | // |
5 | | // This file is licensed under the GPLv2 or later |
6 | | // |
7 | | // Copyright 2013, 2014 Igalia S.L. |
8 | | // Copyright 2014 Luigi Scarso <luigi.scarso@gmail.com> |
9 | | // Copyright 2014, 2017-2019, 2021, 2023-2026 Albert Astals Cid <aacid@kde.org> |
10 | | // Copyright 2015 Dmytro Morgun <lztoad@gmail.com> |
11 | | // Copyright 2018, 2021, 2023 Adrian Johnson <ajohnson@redneon.com> |
12 | | // Copyright 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich |
13 | | // Copyright 2018 Adam Reichold <adam.reichold@t-online.de> |
14 | | // Copyright 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
15 | | // Copyright (C) 2026 Adam Sampson <ats@offog.org> |
16 | | // Copyright 2026 Stefan Brüns <stefan.bruens@rwth-aachen.de> |
17 | | // |
18 | | //======================================================================== |
19 | | |
20 | | #include "StructElement.h" |
21 | | #include "StructTreeRoot.h" |
22 | | #include "GlobalParams.h" |
23 | | #include "PDFDoc.h" |
24 | | #include "Dict.h" |
25 | | |
26 | | #include <cassert> |
27 | | |
28 | | class GfxState; |
29 | | |
30 | | static bool isPlacementName(Object *value) |
31 | 1.34k | { |
32 | 1.34k | return value->isName("Block") || value->isName("Inline") || value->isName("Before") || value->isName("Start") || value->isName("End"); |
33 | 1.34k | } |
34 | | |
35 | | static bool isWritingModeName(Object *value) |
36 | 845 | { |
37 | 845 | return value->isName("LrTb") || value->isName("RlTb") || value->isName("TbRl"); |
38 | 845 | } |
39 | | |
40 | | static bool isBorderStyleName(Object *value) |
41 | 8 | { |
42 | 8 | return value->isName("None") || value->isName("Hidden") || value->isName("Dotted") || value->isName("Dashed") || value->isName("Solid") || value->isName("Double") || value->isName("Groove") || value->isName("Ridge") |
43 | 0 | || value->isName("Inset") || value->isName("Outset"); |
44 | 8 | } |
45 | | |
46 | | static bool isTextAlignName(Object *value) |
47 | 4.52k | { |
48 | 4.52k | return value->isName("Start") || value->isName("End") || value->isName("Center") || value->isName("Justify"); |
49 | 4.52k | } |
50 | | |
51 | | static bool isBlockAlignName(Object *value) |
52 | 248 | { |
53 | 248 | return value->isName("Before") || value->isName("Middle") || value->isName("After") || value->isName("Justify"); |
54 | 248 | } |
55 | | |
56 | | static bool isInlineAlignName(Object *value) |
57 | 120 | { |
58 | 120 | return value->isName("Start") || value->isName("End") || value->isName("Center"); |
59 | 120 | } |
60 | | |
61 | | static bool isNumber(Object *value) |
62 | 11.3k | { |
63 | 11.3k | return value->isNum(); |
64 | 11.3k | } |
65 | | |
66 | | static bool isLineHeight(Object *value) |
67 | 727 | { |
68 | 727 | return value->isName("Normal") || value->isName("Auto") || isNumber(value); |
69 | 727 | } |
70 | | |
71 | | static bool isTextDecorationName(Object *value) |
72 | 0 | { |
73 | 0 | return value->isName("None") || value->isName("Underline") || value->isName("Overline") || value->isName("LineThrough"); |
74 | 0 | } |
75 | | |
76 | | static bool isRubyAlignName(Object *value) |
77 | 0 | { |
78 | 0 | return value->isName("Start") || value->isName("End") || value->isName("Center") || value->isName("Justify") || value->isName("Distribute"); |
79 | 0 | } |
80 | | |
81 | | static bool isRubyPositionName(Object *value) |
82 | 0 | { |
83 | 0 | return value->isName("Before") || value->isName("After") || value->isName("Warichu") || value->isName("Inline"); |
84 | 0 | } |
85 | | |
86 | | static bool isGlyphOrientationName(Object *value) |
87 | 0 | { |
88 | 0 | return value->isName("Auto") || value->isName("90") || value->isName("180") || value->isName("270") || value->isName("360") || value->isName("-90") || value->isName("-180"); |
89 | 0 | } |
90 | | |
91 | | static bool isListNumberingName(Object *value) |
92 | 7 | { |
93 | 7 | return value->isName("None") || value->isName("Disc") || value->isName("Circle") || value->isName("Square") || value->isName("Decimal") || value->isName("UpperRoman") || value->isName("LowerRoman") || value->isName("UpperAlpha") |
94 | 7 | || value->isName("LowerAlpha"); |
95 | 7 | } |
96 | | |
97 | | static bool isFieldRoleName(Object *value) |
98 | 0 | { |
99 | 0 | return value->isName("rb") || value->isName("cb") || value->isName("pb") || value->isName("tv"); |
100 | 0 | } |
101 | | |
102 | | static bool isFieldCheckedName(Object *value) |
103 | 0 | { |
104 | 0 | return value->isName("on") || value->isName("off") || value->isName("neutral"); |
105 | 0 | } |
106 | | |
107 | | static bool isTableScopeName(Object *value) |
108 | 16 | { |
109 | 16 | return value->isName("Row") || value->isName("Column") || value->isName("Both"); |
110 | 16 | } |
111 | | |
112 | | static bool isRGBColor(Object *value) |
113 | 3 | { |
114 | 3 | if (!value->isArrayOfLength(3)) { |
115 | 3 | return false; |
116 | 3 | } |
117 | | |
118 | 0 | bool okay = true; |
119 | 0 | for (int i = 0; i < 3; i++) { |
120 | 0 | Object obj = value->arrayGet(i); |
121 | 0 | if (!obj.isNum()) { |
122 | 0 | okay = false; |
123 | 0 | break; |
124 | 0 | } |
125 | 0 | if (obj.getNum() < 0.0 || obj.getNum() > 1.0) { |
126 | 0 | okay = false; |
127 | 0 | break; |
128 | 0 | } |
129 | 0 | } |
130 | |
|
131 | 0 | return okay; |
132 | 3 | } |
133 | | |
134 | | static bool isNatural(Object *value) |
135 | 200 | { |
136 | 200 | return (value->isInt() && value->getInt() > 0) || (value->isInt64() && value->getInt64() > 0); |
137 | 200 | } |
138 | | |
139 | | static bool isPositive(Object *value) |
140 | 4.60k | { |
141 | 4.60k | return value->isNum() && value->getNum() >= 0.0; |
142 | 4.60k | } |
143 | | |
144 | | static bool isNumberOrAuto(Object *value) |
145 | 263 | { |
146 | 263 | return isNumber(value) || value->isName("Auto"); |
147 | 263 | } |
148 | | |
149 | | static bool isTextString(Object *value) |
150 | 96 | { |
151 | | // XXX: Shall isName() also be checked? |
152 | 96 | return value->isString(); |
153 | 96 | } |
154 | | |
155 | | #define ARRAY_CHECKER(name, checkItem, length, allowSingle, allowNulls) \ |
156 | | static bool name(Object *value) \ |
157 | 706 | { \ |
158 | 706 | if (!value->isArray()) \ |
159 | 706 | return (allowSingle) ? checkItem(value) : false; \ |
160 | 706 | \ |
161 | 706 | if ((length) && value->arrayGetLength() != (length)) \ |
162 | 672 | return false; \ |
163 | 672 | \ |
164 | 672 | bool okay = true; \ |
165 | 2.70k | for (int i = 0; i < value->arrayGetLength(); i++) { \ |
166 | 2.12k | Object obj = value->arrayGet(i); \ |
167 | 2.12k | if ((!(allowNulls) && obj.isNull()) || !checkItem(&obj)) { \ |
168 | 35 | okay = false; \ |
169 | 35 | break; \ |
170 | 35 | } \ |
171 | 2.12k | } \ |
172 | 616 | return okay; \ |
173 | 672 | } StructElement.cc:isRGBColorOrOptionalArray4(Object*) Line | Count | Source | 157 | 8 | { \ | 158 | 8 | if (!value->isArray()) \ | 159 | 8 | return (allowSingle) ? checkItem(value) : false; \ | 160 | 8 | \ | 161 | 8 | if ((length) && value->arrayGetLength() != (length)) \ | 162 | 8 | return false; \ | 163 | 8 | \ | 164 | 8 | bool okay = true; \ | 165 | 0 | for (int i = 0; i < value->arrayGetLength(); i++) { \ | 166 | 0 | Object obj = value->arrayGet(i); \ | 167 | 0 | if ((!(allowNulls) && obj.isNull()) || !checkItem(&obj)) { \ | 168 | 0 | okay = false; \ | 169 | 0 | break; \ | 170 | 0 | } \ | 171 | 0 | } \ | 172 | 0 | return okay; \ | 173 | 8 | } |
StructElement.cc:isBorderStyle(Object*) Line | Count | Source | 157 | 8 | { \ | 158 | 8 | if (!value->isArray()) \ | 159 | 8 | return (allowSingle) ? checkItem(value) : false; \ | 160 | 8 | \ | 161 | 8 | if ((length) && value->arrayGetLength() != (length)) \ | 162 | 0 | return false; \ | 163 | 0 | \ | 164 | 0 | bool okay = true; \ | 165 | 0 | for (int i = 0; i < value->arrayGetLength(); i++) { \ | 166 | 0 | Object obj = value->arrayGet(i); \ | 167 | 0 | if ((!(allowNulls) && obj.isNull()) || !checkItem(&obj)) { \ | 168 | 0 | okay = false; \ | 169 | 0 | break; \ | 170 | 0 | } \ | 171 | 0 | } \ | 172 | 0 | return okay; \ | 173 | 0 | } |
StructElement.cc:isPositiveOrOptionalArray4(Object*) Line | Count | Source | 157 | 8 | { \ | 158 | 8 | if (!value->isArray()) \ | 159 | 8 | return (allowSingle) ? checkItem(value) : false; \ | 160 | 8 | \ | 161 | 8 | if ((length) && value->arrayGetLength() != (length)) \ | 162 | 0 | return false; \ | 163 | 0 | \ | 164 | 0 | bool okay = true; \ | 165 | 0 | for (int i = 0; i < value->arrayGetLength(); i++) { \ | 166 | 0 | Object obj = value->arrayGet(i); \ | 167 | 0 | if ((!(allowNulls) && obj.isNull()) || !checkItem(&obj)) { \ | 168 | 0 | okay = false; \ | 169 | 0 | break; \ | 170 | 0 | } \ | 171 | 0 | } \ | 172 | 0 | return okay; \ | 173 | 0 | } |
Unexecuted instantiation: StructElement.cc:isPositiveOrArray4(Object*) StructElement.cc:isNumberArray4(Object*) Line | Count | Source | 157 | 584 | { \ | 158 | 584 | if (!value->isArray()) \ | 159 | 584 | return (allowSingle) ? checkItem(value) : false; \ | 160 | 584 | \ | 161 | 584 | if ((length) && value->arrayGetLength() != (length)) \ | 162 | 566 | return false; \ | 163 | 566 | \ | 164 | 566 | bool okay = true; \ | 165 | 2.51k | for (int i = 0; i < value->arrayGetLength(); i++) { \ | 166 | 2.03k | Object obj = value->arrayGet(i); \ | 167 | 2.03k | if ((!(allowNulls) && obj.isNull()) || !checkItem(&obj)) { \ | 168 | 35 | okay = false; \ | 169 | 35 | break; \ | 170 | 35 | } \ | 171 | 2.03k | } \ | 172 | 518 | return okay; \ | 173 | 566 | } |
Unexecuted instantiation: StructElement.cc:isNumberOrArrayN(Object*) StructElement.cc:isTableHeaders(Object*) Line | Count | Source | 157 | 98 | { \ | 158 | 98 | if (!value->isArray()) \ | 159 | 98 | return (allowSingle) ? checkItem(value) : false; \ | 160 | 98 | \ | 161 | 98 | if ((length) && value->arrayGetLength() != (length)) \ | 162 | 98 | return false; \ | 163 | 98 | \ | 164 | 98 | bool okay = true; \ | 165 | 194 | for (int i = 0; i < value->arrayGetLength(); i++) { \ | 166 | 96 | Object obj = value->arrayGet(i); \ | 167 | 96 | if ((!(allowNulls) && obj.isNull()) || !checkItem(&obj)) { \ | 168 | 0 | okay = false; \ | 169 | 0 | break; \ | 170 | 0 | } \ | 171 | 96 | } \ | 172 | 98 | return okay; \ | 173 | 98 | } |
|
174 | | |
175 | | ARRAY_CHECKER(isRGBColorOrOptionalArray4, isRGBColor, 4, true, true) |
176 | | ARRAY_CHECKER(isPositiveOrOptionalArray4, isPositive, 4, true, true) |
177 | | ARRAY_CHECKER(isPositiveOrArray4, isPositive, 4, true, false) |
178 | | ARRAY_CHECKER(isBorderStyle, isBorderStyleName, 4, true, true) |
179 | | ARRAY_CHECKER(isNumberArray4, isNumber, 4, false, false) |
180 | | ARRAY_CHECKER(isNumberOrArrayN, isNumber, 0, true, false) |
181 | | ARRAY_CHECKER(isTableHeaders, isTextString, 0, false, false) |
182 | | |
183 | | // Type of functions used to do type-checking on attribute values |
184 | | using AttributeCheckFunc = bool (*)(Object *); |
185 | | |
186 | | // Maps attributes to their names and whether the attribute can be inherited. |
187 | | struct AttributeMapEntry |
188 | | { |
189 | | Attribute::Type type; |
190 | | const char *name; |
191 | | const Object *defval; |
192 | | bool inherit; |
193 | | AttributeCheckFunc check; |
194 | | }; |
195 | | |
196 | | struct AttributeDefaults |
197 | | { |
198 | 34 | AttributeDefaults() = default; // needed to support old clang |
199 | | |
200 | | Object Inline = Object::name("Inline"); |
201 | | Object LrTb = Object::name("LrTb"); |
202 | | Object Normal = Object::name("Normal"); |
203 | | Object Distribute = Object::name("Distribute"); |
204 | | Object off = Object::name("off"); |
205 | | Object Zero = Object(0.0); |
206 | | Object Auto = Object::name("Auto"); |
207 | | Object Start = Object::name("Start"); |
208 | | Object None = Object::name("None"); |
209 | | Object Before = Object::name("Before"); |
210 | | Object Nat1 = Object(1); |
211 | | }; |
212 | | |
213 | | static const AttributeDefaults attributeDefaults; |
214 | | |
215 | | #define ATTR_LIST_END { Attribute::Unknown, nullptr, nullptr, false, nullptr } |
216 | | |
217 | | #define ATTR_WITH_DEFAULT(name, inherit, check, defval) { Attribute::name, #name, &attributeDefaults.defval, inherit, check } |
218 | | |
219 | | #define ATTR(name, inherit, check) { Attribute::name, #name, nullptr, inherit, check } |
220 | | |
221 | | static const AttributeMapEntry attributeMapCommonShared[] = { ATTR_WITH_DEFAULT(Placement, false, isPlacementName, Inline), |
222 | | ATTR_WITH_DEFAULT(WritingMode, true, isWritingModeName, LrTb), |
223 | | ATTR(BackgroundColor, false, isRGBColor), |
224 | | ATTR(BorderColor, true, isRGBColorOrOptionalArray4), |
225 | | ATTR_WITH_DEFAULT(BorderStyle, false, isBorderStyle, None), |
226 | | ATTR(BorderThickness, true, isPositiveOrOptionalArray4), |
227 | | ATTR_WITH_DEFAULT(Padding, false, isPositiveOrArray4, Zero), |
228 | | ATTR(Color, true, isRGBColor), |
229 | | ATTR_LIST_END }; |
230 | | |
231 | | static const AttributeMapEntry attributeMapCommonBlock[] = { ATTR_WITH_DEFAULT(SpaceBefore, false, isPositive, Zero), |
232 | | ATTR_WITH_DEFAULT(SpaceAfter, false, isPositive, Zero), |
233 | | ATTR_WITH_DEFAULT(StartIndent, true, isNumber, Zero), |
234 | | ATTR_WITH_DEFAULT(EndIndent, true, isNumber, Zero), |
235 | | ATTR_WITH_DEFAULT(TextIndent, true, isNumber, Zero), |
236 | | ATTR_WITH_DEFAULT(TextAlign, true, isTextAlignName, Start), |
237 | | ATTR(BBox, false, isNumberArray4), |
238 | | ATTR_WITH_DEFAULT(Width, false, isNumberOrAuto, Auto), |
239 | | ATTR_WITH_DEFAULT(Height, false, isNumberOrAuto, Auto), |
240 | | ATTR_WITH_DEFAULT(BlockAlign, true, isBlockAlignName, Before), |
241 | | ATTR_WITH_DEFAULT(InlineAlign, true, isInlineAlignName, Start), |
242 | | ATTR_LIST_END }; |
243 | | |
244 | | static const AttributeMapEntry attributeMapCommonInline[] = { ATTR_WITH_DEFAULT(BaselineShift, false, isNumber, Zero), |
245 | | ATTR_WITH_DEFAULT(LineHeight, true, isLineHeight, Normal), |
246 | | ATTR(TextDecorationColor, true, isRGBColor), |
247 | | ATTR(TextDecorationThickness, true, isPositive), |
248 | | ATTR_WITH_DEFAULT(TextDecorationType, false, isTextDecorationName, None), |
249 | | ATTR_WITH_DEFAULT(GlyphOrientationVertical, true, isGlyphOrientationName, Auto), |
250 | | ATTR_LIST_END }; |
251 | | |
252 | | static const AttributeMapEntry attributeMapCommonRubyText[] = { ATTR_WITH_DEFAULT(RubyPosition, true, isRubyPositionName, Before), ATTR_WITH_DEFAULT(RubyAlign, true, isRubyAlignName, Distribute), ATTR_LIST_END }; |
253 | | |
254 | | static const AttributeMapEntry attributeMapCommonColumns[] = { ATTR_WITH_DEFAULT(ColumnCount, false, isNatural, Nat1), ATTR(ColumnGap, false, isNumberOrArrayN), ATTR(ColumnWidths, false, isNumberOrArrayN), ATTR_LIST_END }; |
255 | | |
256 | | static const AttributeMapEntry attributeMapCommonList[] = { ATTR_WITH_DEFAULT(ListNumbering, true, isListNumberingName, None), ATTR_LIST_END }; |
257 | | |
258 | | static const AttributeMapEntry attributeMapCommonPrintField[] = { ATTR(Role, false, isFieldRoleName), ATTR_WITH_DEFAULT(checked, false, isFieldCheckedName, off), ATTR(Desc, false, isTextString), ATTR_LIST_END }; |
259 | | |
260 | | static const AttributeMapEntry attributeMapCommonTable[] = { ATTR(Headers, false, isTableHeaders), ATTR(Scope, false, isTableScopeName), ATTR(Summary, false, isTextString), ATTR_LIST_END }; |
261 | | |
262 | | static const AttributeMapEntry attributeMapCommonTableCell[] = { ATTR_WITH_DEFAULT(RowSpan, false, isNatural, Nat1), ATTR_WITH_DEFAULT(ColSpan, false, isNatural, Nat1), ATTR_WITH_DEFAULT(TBorderStyle, true, isBorderStyle, None), |
263 | | ATTR_WITH_DEFAULT(TPadding, true, isPositiveOrArray4, Zero), ATTR_LIST_END }; |
264 | | |
265 | | #undef ATTR_WITH_DEFAULT |
266 | | #undef ATTR |
267 | | |
268 | | static const AttributeMapEntry *attributeMapAll[] = { |
269 | | attributeMapCommonShared, attributeMapCommonBlock, attributeMapCommonInline, attributeMapCommonRubyText, attributeMapCommonColumns, |
270 | | attributeMapCommonList, attributeMapCommonPrintField, attributeMapCommonTable, attributeMapCommonTableCell, nullptr, |
271 | | }; |
272 | | |
273 | | static const AttributeMapEntry *attributeMapShared[] = { |
274 | | attributeMapCommonShared, |
275 | | nullptr, |
276 | | }; |
277 | | |
278 | | static const AttributeMapEntry *attributeMapBlock[] = { |
279 | | attributeMapCommonShared, |
280 | | attributeMapCommonBlock, |
281 | | nullptr, |
282 | | }; |
283 | | |
284 | | static const AttributeMapEntry *attributeMapInline[] = { |
285 | | attributeMapCommonShared, |
286 | | attributeMapCommonInline, |
287 | | nullptr, |
288 | | }; |
289 | | |
290 | | static const AttributeMapEntry *attributeMapTableCell[] = { |
291 | | attributeMapCommonShared, attributeMapCommonBlock, attributeMapCommonTable, attributeMapCommonTableCell, nullptr, |
292 | | }; |
293 | | |
294 | | static const AttributeMapEntry *attributeMapRubyText[] = { |
295 | | attributeMapCommonShared, |
296 | | attributeMapCommonInline, |
297 | | attributeMapCommonRubyText, |
298 | | nullptr, |
299 | | }; |
300 | | |
301 | | static const AttributeMapEntry *attributeMapColumns[] = { |
302 | | attributeMapCommonShared, |
303 | | attributeMapCommonInline, |
304 | | attributeMapCommonColumns, |
305 | | nullptr, |
306 | | }; |
307 | | |
308 | | static const AttributeMapEntry *attributeMapList[] = { |
309 | | attributeMapCommonShared, |
310 | | attributeMapCommonList, |
311 | | nullptr, |
312 | | }; |
313 | | |
314 | | static const AttributeMapEntry *attributeMapTable[] = { |
315 | | attributeMapCommonShared, |
316 | | attributeMapCommonBlock, |
317 | | attributeMapCommonTable, |
318 | | nullptr, |
319 | | }; |
320 | | |
321 | | static const AttributeMapEntry *attributeMapIllustration[] = { |
322 | | // XXX: Illustrations may have some attributes from the "shared", "inline", |
323 | | // the "block" sets. This is a loose specification; making it better |
324 | | // means duplicating entries from the sets. This seems good enough... |
325 | | attributeMapCommonShared, |
326 | | attributeMapCommonBlock, |
327 | | attributeMapCommonInline, |
328 | | nullptr, |
329 | | }; |
330 | | |
331 | | // Table mapping owners of attributes to their names. |
332 | | static const struct OwnerMapEntry |
333 | | { |
334 | | Attribute::Owner owner; |
335 | | const char *name; |
336 | | } ownerMap[] = { |
337 | | // XXX: Those are sorted in the owner priority resolution order. If the |
338 | | // same attribute is defined with two owners, the order in the table |
339 | | // can be used to know which one has more priority. |
340 | | { .owner = Attribute::XML_1_00, .name = "XML-1.00" }, { .owner = Attribute::HTML_3_20, .name = "HTML-3.20" }, { .owner = Attribute::HTML_4_01, .name = "HTML-4.01" }, { .owner = Attribute::OEB_1_00, .name = "OEB-1.00" }, |
341 | | { .owner = Attribute::RTF_1_05, .name = "RTF-1.05" }, { .owner = Attribute::CSS_1_00, .name = "CSS-1.00" }, { .owner = Attribute::CSS_2_00, .name = "CSS-2.00" }, { .owner = Attribute::Layout, .name = "Layout" }, |
342 | | { .owner = Attribute::PrintField, .name = "PrintField" }, { .owner = Attribute::Table, .name = "Table" }, { .owner = Attribute::List, .name = "List" }, { .owner = Attribute::UserProperties, .name = "UserProperties" }, |
343 | | }; |
344 | | |
345 | | static bool ownerHasMorePriority(Attribute::Owner a, Attribute::Owner b) |
346 | 0 | { |
347 | 0 | size_t aIndex, bIndex, i; |
348 | |
|
349 | 0 | for (i = aIndex = bIndex = 0; i < sizeof(ownerMap) / sizeof(ownerMap[0]); i++) { |
350 | 0 | if (ownerMap[i].owner == a) { |
351 | 0 | aIndex = i; |
352 | 0 | } |
353 | 0 | if (ownerMap[i].owner == b) { |
354 | 0 | bIndex = i; |
355 | 0 | } |
356 | 0 | } |
357 | |
|
358 | 0 | return aIndex < bIndex; |
359 | 0 | } |
360 | | |
361 | | // Maps element types to their names and also serves as lookup table |
362 | | // for additional element type attributes. |
363 | | |
364 | | enum ElementType |
365 | | { |
366 | | elementTypeUndefined, |
367 | | elementTypeGrouping, |
368 | | elementTypeInline, |
369 | | elementTypeBlock, |
370 | | }; |
371 | | |
372 | | static const struct TypeMapEntry |
373 | | { |
374 | | StructElement::Type type; |
375 | | const std::string name; |
376 | | ElementType elementType; |
377 | | const AttributeMapEntry **attributes; |
378 | | } typeMap[] = { |
379 | | { .type = StructElement::Document, .name = "Document", .elementType = elementTypeGrouping, .attributes = attributeMapShared }, |
380 | | { .type = StructElement::Part, .name = "Part", .elementType = elementTypeGrouping, .attributes = attributeMapShared }, |
381 | | { .type = StructElement::Art, .name = "Art", .elementType = elementTypeGrouping, .attributes = attributeMapColumns }, |
382 | | { .type = StructElement::Sect, .name = "Sect", .elementType = elementTypeGrouping, .attributes = attributeMapColumns }, |
383 | | { .type = StructElement::Div, .name = "Div", .elementType = elementTypeGrouping, .attributes = attributeMapColumns }, |
384 | | { .type = StructElement::BlockQuote, .name = "BlockQuote", .elementType = elementTypeGrouping, .attributes = attributeMapInline }, |
385 | | { .type = StructElement::Caption, .name = "Caption", .elementType = elementTypeGrouping, .attributes = attributeMapInline }, |
386 | | { .type = StructElement::NonStruct, .name = "NonStruct", .elementType = elementTypeGrouping, .attributes = attributeMapInline }, |
387 | | { .type = StructElement::Index, .name = "Index", .elementType = elementTypeGrouping, .attributes = attributeMapInline }, |
388 | | { .type = StructElement::Private, .name = "Private", .elementType = elementTypeGrouping, .attributes = attributeMapInline }, |
389 | | { .type = StructElement::Span, .name = "Span", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
390 | | { .type = StructElement::Quote, .name = "Quote", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
391 | | { .type = StructElement::Note, .name = "Note", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
392 | | { .type = StructElement::Reference, .name = "Reference", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
393 | | { .type = StructElement::BibEntry, .name = "BibEntry", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
394 | | { .type = StructElement::Code, .name = "Code", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
395 | | { .type = StructElement::Link, .name = "Link", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
396 | | { .type = StructElement::Annot, .name = "Annot", .elementType = elementTypeInline, .attributes = attributeMapInline }, |
397 | | { .type = StructElement::Ruby, .name = "Ruby", .elementType = elementTypeInline, .attributes = attributeMapRubyText }, |
398 | | { .type = StructElement::RB, .name = "RB", .elementType = elementTypeUndefined, .attributes = attributeMapRubyText }, |
399 | | { .type = StructElement::RT, .name = "RT", .elementType = elementTypeUndefined, .attributes = attributeMapRubyText }, |
400 | | { .type = StructElement::RP, .name = "RP", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
401 | | { .type = StructElement::Warichu, .name = "Warichu", .elementType = elementTypeInline, .attributes = attributeMapRubyText }, |
402 | | { .type = StructElement::WT, .name = "WT", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
403 | | { .type = StructElement::WP, .name = "WP", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
404 | | { .type = StructElement::P, .name = "P", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
405 | | { .type = StructElement::H, .name = "H", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
406 | | { .type = StructElement::H1, .name = "H1", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
407 | | { .type = StructElement::H2, .name = "H2", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
408 | | { .type = StructElement::H3, .name = "H3", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
409 | | { .type = StructElement::H4, .name = "H4", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
410 | | { .type = StructElement::H5, .name = "H5", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
411 | | { .type = StructElement::H6, .name = "H6", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
412 | | { .type = StructElement::L, .name = "L", .elementType = elementTypeBlock, .attributes = attributeMapList }, |
413 | | { .type = StructElement::LI, .name = "LI", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
414 | | { .type = StructElement::Lbl, .name = "Lbl", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
415 | | { .type = StructElement::LBody, .name = "LBody", .elementType = elementTypeBlock, .attributes = attributeMapBlock }, |
416 | | { .type = StructElement::Table, .name = "Table", .elementType = elementTypeBlock, .attributes = attributeMapTable }, |
417 | | { .type = StructElement::TR, .name = "TR", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
418 | | { .type = StructElement::TH, .name = "TH", .elementType = elementTypeUndefined, .attributes = attributeMapTableCell }, |
419 | | { .type = StructElement::TD, .name = "TD", .elementType = elementTypeUndefined, .attributes = attributeMapTableCell }, |
420 | | { .type = StructElement::THead, .name = "THead", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
421 | | { .type = StructElement::TFoot, .name = "TFoot", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
422 | | { .type = StructElement::TBody, .name = "TBody", .elementType = elementTypeUndefined, .attributes = attributeMapShared }, |
423 | | { .type = StructElement::Figure, .name = "Figure", .elementType = elementTypeUndefined, .attributes = attributeMapIllustration }, |
424 | | { .type = StructElement::Formula, .name = "Formula", .elementType = elementTypeUndefined, .attributes = attributeMapIllustration }, |
425 | | { .type = StructElement::Form, .name = "Form", .elementType = elementTypeUndefined, .attributes = attributeMapIllustration }, |
426 | | { .type = StructElement::TOC, .name = "TOC", .elementType = elementTypeGrouping, .attributes = attributeMapShared }, |
427 | | { .type = StructElement::TOCI, .name = "TOCI", .elementType = elementTypeGrouping, .attributes = attributeMapShared }, |
428 | | }; |
429 | | |
430 | | //------------------------------------------------------------------------ |
431 | | // Helpers for the attribute and structure type tables |
432 | | //------------------------------------------------------------------------ |
433 | | |
434 | | static inline const AttributeMapEntry *getAttributeMapEntry(const AttributeMapEntry **entryList, Attribute::Type type) |
435 | 22.9k | { |
436 | 22.9k | assert(entryList); |
437 | 44.1k | while (*entryList) { |
438 | 44.1k | const AttributeMapEntry *entry = *entryList; |
439 | 273k | while (entry->type != Attribute::Unknown) { |
440 | 252k | if (type == entry->type) { |
441 | 22.9k | return entry; |
442 | 22.9k | } |
443 | 229k | entry++; |
444 | 229k | } |
445 | 21.1k | entryList++; |
446 | 21.1k | } |
447 | 0 | return nullptr; |
448 | 22.9k | } |
449 | | |
450 | | static inline const AttributeMapEntry *getAttributeMapEntry(const AttributeMapEntry **entryList, const std::string &name) |
451 | 34.7k | { |
452 | 34.7k | assert(entryList); |
453 | 76.0k | while (*entryList) { |
454 | 67.5k | const AttributeMapEntry *entry = *entryList; |
455 | 487k | while (entry->type != Attribute::Unknown) { |
456 | 446k | assert(entry->name); |
457 | 446k | if (name == entry->name) { |
458 | 26.2k | return entry; |
459 | 26.2k | } |
460 | 420k | entry++; |
461 | 420k | } |
462 | 41.2k | entryList++; |
463 | 41.2k | } |
464 | 8.48k | return nullptr; |
465 | 34.7k | } |
466 | | |
467 | | static inline const OwnerMapEntry *getOwnerMapEntry(Attribute::Owner owner) |
468 | 0 | { |
469 | 0 | for (const OwnerMapEntry &entry : ownerMap) { |
470 | 0 | if (owner == entry.owner) { |
471 | 0 | return &entry; |
472 | 0 | } |
473 | 0 | } |
474 | 0 | return nullptr; |
475 | 0 | } |
476 | | |
477 | | static inline const OwnerMapEntry *getOwnerMapEntry(std::string_view name) |
478 | 12.4k | { |
479 | 102k | for (const OwnerMapEntry &entry : ownerMap) { |
480 | 102k | if (name == entry.name) { |
481 | 11.9k | return &entry; |
482 | 11.9k | } |
483 | 102k | } |
484 | 484 | return nullptr; |
485 | 12.4k | } |
486 | | |
487 | | static const char *ownerToName(Attribute::Owner owner) |
488 | 0 | { |
489 | 0 | const OwnerMapEntry *entry = getOwnerMapEntry(owner); |
490 | 0 | return entry ? entry->name : "UnknownOwner"; |
491 | 0 | } |
492 | | |
493 | | static Attribute::Owner nameToOwner(std::string_view name) |
494 | 12.4k | { |
495 | 12.4k | const OwnerMapEntry *entry = getOwnerMapEntry(name); |
496 | 12.4k | return entry ? entry->owner : Attribute::UnknownOwner; |
497 | 12.4k | } |
498 | | |
499 | | static inline const TypeMapEntry *getTypeMapEntry(StructElement::Type type) |
500 | 66.2k | { |
501 | 1.82M | for (const TypeMapEntry &entry : typeMap) { |
502 | 1.82M | if (type == entry.type) { |
503 | 66.2k | return &entry; |
504 | 66.2k | } |
505 | 1.82M | } |
506 | 0 | return nullptr; |
507 | 66.2k | } |
508 | | |
509 | | static inline const TypeMapEntry *getTypeMapEntry(std::string_view name) |
510 | 74.8k | { |
511 | 1.77M | for (const TypeMapEntry &entry : typeMap) { |
512 | 1.77M | if (name == entry.name) { |
513 | 70.9k | return &entry; |
514 | 70.9k | } |
515 | 1.77M | } |
516 | 3.88k | return nullptr; |
517 | 74.8k | } |
518 | | |
519 | | static const char *typeToName(StructElement::Type type) |
520 | 9.58k | { |
521 | 9.58k | if (type == StructElement::MCID) { |
522 | 0 | return "MarkedContent"; |
523 | 0 | } |
524 | 9.58k | if (type == StructElement::OBJR) { |
525 | 0 | return "ObjectReference"; |
526 | 0 | } |
527 | | |
528 | 9.58k | const TypeMapEntry *entry = getTypeMapEntry(type); |
529 | 9.58k | return entry ? entry->name.c_str() : "Unknown"; |
530 | 9.58k | } |
531 | | |
532 | | static StructElement::Type nameToType(std::string_view name) |
533 | 74.8k | { |
534 | 74.8k | const TypeMapEntry *entry = getTypeMapEntry(name); |
535 | 74.8k | return entry ? entry->type : StructElement::Unknown; |
536 | 74.8k | } |
537 | | |
538 | | //------------------------------------------------------------------------ |
539 | | // Attribute |
540 | | //------------------------------------------------------------------------ |
541 | | |
542 | 0 | Attribute::Attribute(GooString &&nameA, Object *valueA) : type(UserProperty), owner(UserProperties), revision(0), name(std::move(nameA)), hidden(false) |
543 | 0 | { |
544 | 0 | assert(valueA); |
545 | 0 | value = valueA->copy(); |
546 | 0 | } |
547 | | |
548 | | Attribute::Attribute(Type typeA, Object *valueA) |
549 | 21.9k | : type(typeA), |
550 | 21.9k | owner(UserProperties), // TODO: Determine corresponding owner from Type |
551 | 21.9k | revision(0), |
552 | 21.9k | hidden(false) |
553 | 21.9k | { |
554 | 21.9k | assert(valueA); |
555 | | |
556 | 21.9k | value = valueA->copy(); |
557 | | |
558 | 21.9k | if (!checkType()) { |
559 | 0 | type = Unknown; |
560 | 0 | } |
561 | 21.9k | } |
562 | | |
563 | 21.9k | Attribute::~Attribute() = default; |
564 | | |
565 | | const char *Attribute::getTypeName() const |
566 | 1.02k | { |
567 | 1.02k | if (type == UserProperty) { |
568 | 0 | return name.c_str(); |
569 | 0 | } |
570 | | |
571 | 1.02k | const AttributeMapEntry *entry = getAttributeMapEntry(attributeMapAll, type); |
572 | 1.02k | if (entry) { |
573 | 1.02k | return entry->name; |
574 | 1.02k | } |
575 | | |
576 | 0 | return "Unknown"; |
577 | 1.02k | } |
578 | | |
579 | | const char *Attribute::getOwnerName() const |
580 | 0 | { |
581 | 0 | return ownerToName(owner); |
582 | 0 | } |
583 | | |
584 | | Object *Attribute::getDefaultValue(Attribute::Type type) |
585 | 0 | { |
586 | 0 | const AttributeMapEntry *entry = getAttributeMapEntry(attributeMapAll, type); |
587 | 0 | return entry ? const_cast<Object *>(entry->defval) : nullptr; |
588 | 0 | } |
589 | | |
590 | | void Attribute::setFormattedValue(const std::optional<std::string> &formattedA) |
591 | 0 | { |
592 | 0 | formatted = formattedA; |
593 | 0 | } |
594 | | |
595 | | bool Attribute::checkType(StructElement *element) |
596 | 43.8k | { |
597 | | // If an element is passed, tighter type-checking can be done. |
598 | 43.8k | if (!element) { |
599 | 21.9k | return true; |
600 | 21.9k | } |
601 | | |
602 | 21.9k | const TypeMapEntry *elementTypeEntry = getTypeMapEntry(element->getType()); |
603 | 21.9k | if (elementTypeEntry && elementTypeEntry->attributes) { |
604 | 21.9k | const AttributeMapEntry *entry = getAttributeMapEntry(elementTypeEntry->attributes, type); |
605 | 21.9k | if (entry) { |
606 | 21.9k | if (entry->check && !((*entry->check)(&value))) { |
607 | 1.02k | return false; |
608 | 1.02k | } |
609 | 21.9k | } else { |
610 | | // No entry: the attribute is not valid for the containing element. |
611 | 0 | return false; |
612 | 0 | } |
613 | 21.9k | } |
614 | | |
615 | 20.9k | return true; |
616 | 21.9k | } |
617 | | |
618 | | Attribute::Type Attribute::getTypeForName(const std::string &name, StructElement *element) |
619 | 34.7k | { |
620 | 34.7k | const AttributeMapEntry **attributes = attributeMapAll; |
621 | 34.7k | if (element) { |
622 | 34.7k | const TypeMapEntry *elementTypeEntry = getTypeMapEntry(element->getType()); |
623 | 34.7k | if (elementTypeEntry && elementTypeEntry->attributes) { |
624 | 34.7k | attributes = elementTypeEntry->attributes; |
625 | 34.7k | } |
626 | 34.7k | } |
627 | | |
628 | 34.7k | const AttributeMapEntry *entry = getAttributeMapEntry(attributes, name); |
629 | 34.7k | return entry ? entry->type : Unknown; |
630 | 34.7k | } |
631 | | |
632 | | Attribute *Attribute::parseUserProperty(Dict *property) |
633 | 0 | { |
634 | 0 | Object obj, value; |
635 | 0 | GooString name; |
636 | |
|
637 | 0 | obj = property->lookup("N"); |
638 | 0 | if (obj.isString()) { |
639 | 0 | name.assign(obj.getString()); |
640 | 0 | } else if (obj.isName()) { |
641 | 0 | name.assign(obj.getNameString()); |
642 | 0 | } else { |
643 | 0 | error(errSyntaxError, -1, "N object is wrong type ({0:s})", obj.getTypeName()); |
644 | 0 | return nullptr; |
645 | 0 | } |
646 | | |
647 | 0 | value = property->lookup("V"); |
648 | 0 | if (value.isNull()) { |
649 | 0 | error(errSyntaxError, -1, "V object is wrong type ({0:s})", value.getTypeName()); |
650 | 0 | return nullptr; |
651 | 0 | } |
652 | | |
653 | 0 | auto *attribute = new Attribute(std::move(name), &value); |
654 | 0 | obj = property->lookup("F"); |
655 | 0 | if (obj.isString()) { |
656 | 0 | attribute->setFormattedValue(obj.getString()); |
657 | 0 | } else if (!obj.isNull()) { |
658 | 0 | error(errSyntaxWarning, -1, "F object is wrong type ({0:s})", obj.getTypeName()); |
659 | 0 | } |
660 | |
|
661 | 0 | obj = property->lookup("H"); |
662 | 0 | if (obj.isBool()) { |
663 | 0 | attribute->setHidden(obj.getBool()); |
664 | 0 | } else if (!obj.isNull()) { |
665 | 0 | error(errSyntaxWarning, -1, "H object is wrong type ({0:s})", obj.getTypeName()); |
666 | 0 | } |
667 | |
|
668 | 0 | return attribute; |
669 | 0 | } |
670 | | |
671 | | //------------------------------------------------------------------------ |
672 | | // StructElement |
673 | | //------------------------------------------------------------------------ |
674 | | |
675 | 83.4k | StructElement::StructData::StructData() : altText(nullptr), actualText(nullptr), id(nullptr), title(nullptr), expandedAbbr(nullptr), language(nullptr) { } |
676 | | |
677 | | StructElement::StructData::~StructData() |
678 | 83.4k | { |
679 | 236k | for (StructElement *element : elements) { |
680 | 236k | delete element; |
681 | 236k | } |
682 | 83.4k | for (Attribute *attribute : attributes) { |
683 | 20.9k | delete attribute; |
684 | 20.9k | } |
685 | 83.4k | } |
686 | | |
687 | 83.4k | StructElement::StructElement(Dict *element, StructTreeRoot *treeRootA, StructElement *parentA, RefRecursionChecker &seen) : type(Unknown), treeRoot(treeRootA), parent(parentA), s(new StructData()) |
688 | 83.4k | { |
689 | 83.4k | assert(treeRoot); |
690 | 83.4k | assert(element); |
691 | | |
692 | 83.4k | parse(element); |
693 | 83.4k | parseChildren(element, seen); |
694 | 83.4k | } |
695 | | |
696 | 170k | StructElement::StructElement(int mcid, StructTreeRoot *treeRootA, StructElement *parentA) : type(MCID), treeRoot(treeRootA), parent(parentA), c(new ContentData(mcid)) |
697 | 170k | { |
698 | 170k | assert(treeRoot); |
699 | 170k | assert(parent); |
700 | 170k | } |
701 | | |
702 | 3.14k | StructElement::StructElement(const Ref ref, StructTreeRoot *treeRootA, StructElement *parentA) : type(OBJR), treeRoot(treeRootA), parent(parentA), c(new ContentData(ref)) |
703 | 3.14k | { |
704 | 3.14k | assert(treeRoot); |
705 | 3.14k | assert(parent); |
706 | 3.14k | } |
707 | | |
708 | | StructElement::~StructElement() |
709 | 257k | { |
710 | 257k | if (isContent()) { |
711 | 173k | delete c; |
712 | 173k | } else { |
713 | 83.4k | delete s; |
714 | 83.4k | } |
715 | 257k | } |
716 | | |
717 | | bool StructElement::isBlock() const |
718 | 0 | { |
719 | 0 | const TypeMapEntry *entry = getTypeMapEntry(type); |
720 | 0 | return entry ? (entry->elementType == elementTypeBlock) : false; |
721 | 0 | } |
722 | | |
723 | | bool StructElement::isInline() const |
724 | 0 | { |
725 | 0 | const TypeMapEntry *entry = getTypeMapEntry(type); |
726 | 0 | return entry ? (entry->elementType == elementTypeInline) : false; |
727 | 0 | } |
728 | | |
729 | | bool StructElement::isGrouping() const |
730 | 0 | { |
731 | 0 | const TypeMapEntry *entry = getTypeMapEntry(type); |
732 | 0 | return entry ? (entry->elementType == elementTypeGrouping) : false; |
733 | 0 | } |
734 | | |
735 | | bool StructElement::hasPageRef() const |
736 | 0 | { |
737 | 0 | return pageRef.isRef() || (parent && parent->hasPageRef()); |
738 | 0 | } |
739 | | |
740 | | bool StructElement::getPageRef(Ref &ref) const |
741 | 552k | { |
742 | 552k | if (pageRef.isRef()) { |
743 | 77.4k | ref = pageRef.getRef(); |
744 | 77.4k | return true; |
745 | 77.4k | } |
746 | | |
747 | 474k | if (parent) { |
748 | 326k | return parent->getPageRef(ref); |
749 | 326k | } |
750 | | |
751 | 148k | return false; |
752 | 474k | } |
753 | | |
754 | | bool StructElement::getStmRef(Ref &ref) const |
755 | 0 | { |
756 | 0 | if (stmRef.isRef()) { |
757 | 0 | ref = stmRef.getRef(); |
758 | 0 | return true; |
759 | 0 | } |
760 | 0 | return false; |
761 | 0 | } |
762 | | |
763 | | const char *StructElement::getTypeName() const |
764 | 9.58k | { |
765 | 9.58k | return typeToName(type); |
766 | 9.58k | } |
767 | | |
768 | | const Attribute *StructElement::findAttribute(Attribute::Type attributeType, bool inherit, Attribute::Owner attributeOwner) const |
769 | 0 | { |
770 | 0 | if (isContent()) { |
771 | 0 | return parent->findAttribute(attributeType, inherit, attributeOwner); |
772 | 0 | } |
773 | | |
774 | 0 | if (attributeType == Attribute::Unknown || attributeType == Attribute::UserProperty) { |
775 | 0 | return nullptr; |
776 | 0 | } |
777 | | |
778 | 0 | const Attribute *result = nullptr; |
779 | |
|
780 | 0 | if (attributeOwner == Attribute::UnknownOwner) { |
781 | | // Search for the attribute, no matter who the owner is |
782 | 0 | for (unsigned i = 0; i < getNumAttributes(); i++) { |
783 | 0 | const Attribute *attr = getAttribute(i); |
784 | 0 | if (attributeType == attr->getType()) { |
785 | 0 | if (!result || ownerHasMorePriority(attr->getOwner(), result->getOwner())) { |
786 | 0 | result = attr; |
787 | 0 | } |
788 | 0 | } |
789 | 0 | } |
790 | 0 | } else { |
791 | | // Search for the attribute, with a specific owner |
792 | 0 | for (unsigned i = 0; i < getNumAttributes(); i++) { |
793 | 0 | const Attribute *attr = getAttribute(i); |
794 | 0 | if (attributeType == attr->getType() && attributeOwner == attr->getOwner()) { |
795 | 0 | result = attr; |
796 | 0 | break; |
797 | 0 | } |
798 | 0 | } |
799 | 0 | } |
800 | |
|
801 | 0 | if (result) { |
802 | 0 | return result; |
803 | 0 | } |
804 | | |
805 | 0 | if (inherit && parent) { |
806 | 0 | const AttributeMapEntry *entry = getAttributeMapEntry(attributeMapAll, attributeType); |
807 | 0 | assert(entry); |
808 | | // TODO: Take into account special inheritance cases, for example: |
809 | | // inline elements which have been changed to be block using |
810 | | // "/Placement/Block" have slightly different rules. |
811 | 0 | if (entry->inherit) { |
812 | 0 | return parent->findAttribute(attributeType, inherit, attributeOwner); |
813 | 0 | } |
814 | 0 | } |
815 | | |
816 | 0 | return nullptr; |
817 | 0 | } |
818 | | |
819 | | GooString *StructElement::appendSubTreeText(GooString *string, bool recursive) const |
820 | 231k | { |
821 | 231k | if (isContent() && !isObjectRef()) { |
822 | 225k | MarkedContentOutputDev mcdev(getMCID(), stmRef); |
823 | 225k | const TextSpanArray &spans(getTextSpansInternal(mcdev)); |
824 | | |
825 | 225k | if (!string) { |
826 | 225k | string = new GooString(); |
827 | 225k | } |
828 | | |
829 | 225k | for (const TextSpan &span : spans) { |
830 | 33.0k | string->append(span.getText()->toStr()); |
831 | 33.0k | } |
832 | | |
833 | 225k | return string; |
834 | 225k | } |
835 | | |
836 | 6.03k | if (!recursive) { |
837 | 3.01k | return nullptr; |
838 | 3.01k | } |
839 | | |
840 | | // Do a depth-first traversal, to get elements in logical order |
841 | 3.01k | if (!string) { |
842 | 3.01k | string = new GooString(); |
843 | 3.01k | } |
844 | | |
845 | 3.01k | for (unsigned i = 0; i < getNumChildren(); i++) { |
846 | 0 | getChild(i)->appendSubTreeText(string, recursive); |
847 | 0 | } |
848 | | |
849 | 3.01k | return string; |
850 | 6.03k | } |
851 | | |
852 | | const TextSpanArray &StructElement::getTextSpansInternal(MarkedContentOutputDev &mcdev) const |
853 | 225k | { |
854 | 225k | assert(isContent()); |
855 | | |
856 | 225k | int startPage = 0, endPage = 0; |
857 | | |
858 | 225k | Ref ref; |
859 | 225k | if (getPageRef(ref)) { |
860 | 77.4k | startPage = endPage = treeRoot->getDoc()->findPage(ref); |
861 | 77.4k | } |
862 | | |
863 | 225k | if (!(startPage && endPage)) { |
864 | 175k | startPage = 1; |
865 | 175k | endPage = treeRoot->getDoc()->getNumPages(); |
866 | 175k | } |
867 | | |
868 | 225k | treeRoot->getDoc()->displayPages(&mcdev, startPage, endPage, 72.0, 72.0, 0, true, false, false); |
869 | 225k | return mcdev.getTextSpans(); |
870 | 225k | } |
871 | | static StructElement::Type roleMapResolve(Dict *roleMap, std::string_view name) |
872 | 69.0k | { |
873 | 69.0k | Object resolved = roleMap->lookup(name); |
874 | 69.0k | std::set<std::string> recursion; |
875 | 69.5k | while (true) { |
876 | 69.5k | if (resolved.isName()) { |
877 | 16.9k | if (!recursion.insert(resolved.getNameString()).second) { |
878 | | // circular reference |
879 | 26 | error(errSyntaxWarning, -1, "RoleMap entries contains circular references"); |
880 | 26 | return StructElement::Unknown; |
881 | 26 | } |
882 | | |
883 | 16.9k | StructElement::Type type = nameToType(resolved.getNameString()); |
884 | 16.9k | if (type != StructElement::Unknown) { |
885 | 16.3k | return type; |
886 | 16.3k | } |
887 | 546 | resolved = roleMap->lookup(resolved.getNameString()); |
888 | 546 | continue; |
889 | 16.9k | } |
890 | | |
891 | 52.6k | if (!resolved.isNull()) { |
892 | 286 | error(errSyntaxWarning, -1, "RoleMap entry is wrong type ({0:s})", resolved.getTypeName()); |
893 | 286 | } |
894 | 52.6k | return StructElement::Unknown; |
895 | 69.5k | } |
896 | 69.0k | } |
897 | | |
898 | | void StructElement::parse(Dict *element) |
899 | 83.4k | { |
900 | 83.4k | Object obj; |
901 | | |
902 | | // Type is optional, but if present must be StructElem |
903 | 83.4k | obj = element->lookup("Type"); |
904 | 83.4k | if (!obj.isNull() && !obj.isName("StructElem")) { |
905 | 2.58k | error(errSyntaxError, -1, "Type of StructElem object is wrong"); |
906 | 2.58k | return; |
907 | 2.58k | } |
908 | | |
909 | | // Parent object reference (required). |
910 | 80.8k | const Object &objP = element->lookupNF("P"); |
911 | 80.8k | if (!objP.isRef()) { |
912 | 6.20k | error(errSyntaxError, -1, "P object is wrong type ({0:s})", obj.getTypeName()); |
913 | 6.20k | return; |
914 | 6.20k | } |
915 | 74.6k | s->parentRef = objP.getRef(); |
916 | | |
917 | | // Check whether the S-type is valid for the top level |
918 | | // element and create a node of the appropriate type. |
919 | 74.6k | obj = element->lookup("S"); |
920 | 74.6k | if (!obj.isName()) { |
921 | 336 | error(errSyntaxError, -1, "S object is wrong type ({0:s})", obj.getTypeName()); |
922 | 336 | return; |
923 | 336 | } |
924 | | |
925 | | // Type name may not be standard, resolve through RoleMap first. |
926 | 74.3k | if (treeRoot->getRoleMap()) { |
927 | 69.0k | type = roleMapResolve(treeRoot->getRoleMap(), obj.getNameString()); |
928 | 69.0k | } |
929 | | |
930 | | // Resolving through RoleMap may leave type as Unknown, e.g. for types |
931 | | // which are not present in it, yet they are standard element types. |
932 | 74.3k | if (type == Unknown) { |
933 | 57.9k | type = nameToType(obj.getNameString()); |
934 | 57.9k | } |
935 | | |
936 | | // At this point either the type name must have been resolved. |
937 | 74.3k | if (type == Unknown) { |
938 | 3.33k | error(errSyntaxError, -1, "StructElem object is wrong type ({0:s})", obj.getName()); |
939 | 3.33k | return; |
940 | 3.33k | } |
941 | | |
942 | | // Object ID (optional), to be looked at the IDTree in the tree root. |
943 | 70.9k | obj = element->lookup("ID"); |
944 | 70.9k | if (obj.isString()) { |
945 | 6.93k | s->id = obj.takeString(); |
946 | 6.93k | } |
947 | | |
948 | | // Page reference (optional) in which at least one of the child items |
949 | | // is to be rendered in. Note: each element stores only the /Pg value |
950 | | // contained by it, and StructElement::getPageRef() may look in parent |
951 | | // elements to find the page where an element belongs. |
952 | 70.9k | pageRef = element->lookupNF("Pg").copy(); |
953 | | |
954 | | // Revision number (optional). |
955 | 70.9k | obj = element->lookup("R"); |
956 | 70.9k | if (obj.isInt()) { |
957 | 159 | s->revision = obj.getInt(); |
958 | 159 | } |
959 | | |
960 | | // Element title (optional). |
961 | 70.9k | obj = element->lookup("T"); |
962 | 70.9k | if (obj.isString()) { |
963 | 2.70k | s->title = obj.takeString(); |
964 | 2.70k | } |
965 | | |
966 | | // Language (optional). |
967 | 70.9k | obj = element->lookup("Lang"); |
968 | 70.9k | if (obj.isString()) { |
969 | 1.09k | s->language = obj.takeString(); |
970 | 1.09k | } |
971 | | |
972 | | // Alternative text (optional). |
973 | 70.9k | obj = element->lookup("Alt"); |
974 | 70.9k | if (obj.isString()) { |
975 | 265 | s->altText = obj.takeString(); |
976 | 265 | } |
977 | | |
978 | | // Expanded form of an abbreviation (optional). |
979 | 70.9k | obj = element->lookup("E"); |
980 | 70.9k | if (obj.isString()) { |
981 | 20 | s->expandedAbbr = obj.takeString(); |
982 | 20 | } |
983 | | |
984 | | // Actual text (optional). |
985 | 70.9k | obj = element->lookup("ActualText"); |
986 | 70.9k | if (obj.isString()) { |
987 | 484 | s->actualText = obj.takeString(); |
988 | 484 | } |
989 | | |
990 | | // Attributes directly attached to the element (optional). |
991 | 70.9k | obj = element->lookup("A"); |
992 | 70.9k | if (obj.isDict()) { |
993 | 2.16k | parseAttributes(obj.getDict()); |
994 | 68.8k | } else if (obj.isArray()) { |
995 | 274 | unsigned attrIndex = getNumAttributes(); |
996 | 3.85k | for (int i = 0; i < obj.arrayGetLength(); i++) { |
997 | 3.58k | Object iobj = obj.arrayGet(i); |
998 | 3.58k | if (iobj.isDict()) { |
999 | 979 | attrIndex = getNumAttributes(); |
1000 | 979 | parseAttributes(iobj.getDict()); |
1001 | 2.60k | } else if (iobj.isInt()) { |
1002 | 1.17k | const int revision = iobj.getInt(); |
1003 | | // Set revision numbers for the elements previously created. |
1004 | 1.21k | for (unsigned j = attrIndex; j < getNumAttributes(); j++) { |
1005 | 42 | getNonConstAttribute(j)->setRevision(revision); |
1006 | 42 | } |
1007 | 1.43k | } else { |
1008 | 1.43k | error(errSyntaxWarning, -1, "A item is wrong type ({0:s})", iobj.getTypeName()); |
1009 | 1.43k | } |
1010 | 3.58k | } |
1011 | 68.5k | } else if (!obj.isNull()) { |
1012 | 123 | error(errSyntaxWarning, -1, "A is wrong type ({0:s})", obj.getTypeName()); |
1013 | 123 | } |
1014 | | |
1015 | | // Attributes referenced indirectly through the ClassMap (optional). |
1016 | 70.9k | if (treeRoot->getClassMap()) { |
1017 | 29.1k | Object classes = element->lookup("C"); |
1018 | 29.1k | if (classes.isName()) { |
1019 | 11.5k | Object attr = treeRoot->getClassMap()->lookup(classes.getNameString()); |
1020 | 11.5k | if (attr.isDict()) { |
1021 | 9.69k | parseAttributes(attr.getDict(), true); |
1022 | 9.69k | } else if (attr.isArray()) { |
1023 | 2.54k | for (int i = 0; i < attr.arrayGetLength(); i++) { |
1024 | 2.26k | unsigned attrIndex = getNumAttributes(); |
1025 | 2.26k | Object iobj = attr.arrayGet(i); |
1026 | 2.26k | if (iobj.isDict()) { |
1027 | 746 | attrIndex = getNumAttributes(); |
1028 | 746 | parseAttributes(iobj.getDict(), true); |
1029 | 1.51k | } else if (iobj.isInt()) { |
1030 | | // Set revision numbers for the elements previously created. |
1031 | 534 | const int revision = iobj.getInt(); |
1032 | 534 | for (unsigned j = attrIndex; j < getNumAttributes(); j++) { |
1033 | 0 | getNonConstAttribute(j)->setRevision(revision); |
1034 | 0 | } |
1035 | 981 | } else { |
1036 | 981 | error(errSyntaxWarning, -1, "C item is wrong type ({0:s})", iobj.getTypeName()); |
1037 | 981 | } |
1038 | 2.26k | } |
1039 | 1.55k | } else if (!attr.isNull()) { |
1040 | 1.02k | error(errSyntaxWarning, -1, "C object is wrong type ({0:s})", classes.getTypeName()); |
1041 | 1.02k | } |
1042 | 11.5k | } |
1043 | 29.1k | } |
1044 | 70.9k | } |
1045 | | |
1046 | | StructElement *StructElement::parseChild(const Object *ref, Object *childObj, RefRecursionChecker &seen) |
1047 | 458k | { |
1048 | 458k | assert(childObj); |
1049 | 458k | assert(ref); |
1050 | | |
1051 | 458k | StructElement *child = nullptr; |
1052 | | |
1053 | 458k | if (childObj->isInt()) { |
1054 | 157k | child = new StructElement(childObj->getInt(), treeRoot, this); |
1055 | 300k | } else if (childObj->isDict("MCR")) { |
1056 | | /* |
1057 | | * TODO: The optional StmOwn attribute is not handled. |
1058 | | */ |
1059 | | |
1060 | 13.9k | Object mcidObj = childObj->dictLookup("MCID"); |
1061 | 13.9k | if (!mcidObj.isInt()) { |
1062 | 983 | error(errSyntaxError, -1, "MCID object is wrong type ({0:s})", mcidObj.getTypeName()); |
1063 | 983 | return nullptr; |
1064 | 983 | } |
1065 | | |
1066 | 13.0k | child = new StructElement(mcidObj.getInt(), treeRoot, this); |
1067 | | |
1068 | 13.0k | Object pageRefObj = childObj->dictLookupNF("Pg").copy(); |
1069 | 13.0k | if (pageRefObj.isRef()) { |
1070 | 12.7k | child->pageRef = std::move(pageRefObj); |
1071 | 12.7k | } |
1072 | | |
1073 | 13.0k | const Object &stmObj = childObj->dictLookupNF("Stm"); |
1074 | 13.0k | if (stmObj.isRef()) { |
1075 | 0 | child->stmRef = stmObj.copy(); |
1076 | 13.0k | } else if (!stmObj.isNull()) { |
1077 | 6 | error(errSyntaxError, -1, "Stm object is wrong type ({0:s})", stmObj.getTypeName()); |
1078 | 6 | delete child; |
1079 | 6 | return nullptr; |
1080 | 6 | } |
1081 | | |
1082 | 286k | } else if (childObj->isDict("OBJR")) { |
1083 | 3.19k | const Object &refObj = childObj->dictLookupNF("Obj"); |
1084 | 3.19k | if (refObj.isRef()) { |
1085 | | |
1086 | 3.14k | child = new StructElement(refObj.getRef(), treeRoot, this); |
1087 | | |
1088 | 3.14k | Object pageRefObj = childObj->dictLookupNF("Pg").copy(); |
1089 | 3.14k | if (pageRefObj.isRef()) { |
1090 | 3.05k | child->pageRef = std::move(pageRefObj); |
1091 | 3.05k | } |
1092 | 3.14k | } else { |
1093 | 43 | error(errSyntaxError, -1, "Obj object is wrong type ({0:s})", refObj.getTypeName()); |
1094 | 43 | } |
1095 | 283k | } else if (childObj->isDict()) { |
1096 | 98.3k | if (!ref->isRef()) { |
1097 | 23.2k | error(errSyntaxError, -1, "Structure element dictionary is not an indirect reference ({0:s})", ref->getTypeName()); |
1098 | 75.0k | } else if (seen.insert(ref->getRef())) { |
1099 | 73.6k | child = new StructElement(childObj->getDict(), treeRoot, this, seen); |
1100 | 73.6k | } else { |
1101 | 1.44k | error(errSyntaxWarning, -1, "Loop detected in structure tree, skipping subtree at object {0:d}:{1:d}", ref->getRefNum(), ref->getRefGen()); |
1102 | 1.44k | } |
1103 | 185k | } else { |
1104 | 185k | error(errSyntaxWarning, -1, "K has a child of wrong type ({0:s})", childObj->getTypeName()); |
1105 | 185k | } |
1106 | | |
1107 | 457k | if (child) { |
1108 | 247k | if (child->isOk()) { |
1109 | 236k | appendChild(child); |
1110 | 236k | if (ref->isRef()) { |
1111 | 67.2k | treeRoot->parentTreeAdd(ref->getRef(), child); |
1112 | 67.2k | } |
1113 | 236k | } else { |
1114 | 10.6k | delete child; |
1115 | 10.6k | child = nullptr; |
1116 | 10.6k | } |
1117 | 247k | } |
1118 | | |
1119 | 457k | return child; |
1120 | 458k | } |
1121 | | |
1122 | | void StructElement::parseChildren(Dict *element, RefRecursionChecker &seen) |
1123 | 83.4k | { |
1124 | 83.4k | Object kids = element->lookup("K"); |
1125 | 83.4k | if (kids.isArray()) { |
1126 | 27.8k | Array *kidsArray = kids.getArray(); |
1127 | 445k | for (int i = 0; i < kidsArray->getLength(); i++) { |
1128 | 417k | Object obj = kidsArray->get(i); |
1129 | 417k | const Object &ref = kidsArray->getNF(i); |
1130 | 417k | parseChild(&ref, &obj, seen); |
1131 | 417k | } |
1132 | 55.6k | } else if (kids.isDict() || kids.isInt()) { |
1133 | 41.2k | const Object &ref = element->lookupNF("K"); |
1134 | 41.2k | parseChild(&ref, &kids, seen); |
1135 | 41.2k | } |
1136 | 83.4k | } |
1137 | | |
1138 | | void StructElement::parseAttributes(Dict *attributes, bool keepExisting) |
1139 | 13.5k | { |
1140 | 13.5k | Object owner = attributes->lookup("O"); |
1141 | 13.5k | if (owner.isName("UserProperties")) { |
1142 | | // In this case /P is an array of UserProperty dictionaries |
1143 | 0 | Object userProperties = attributes->lookup("P"); |
1144 | 0 | if (userProperties.isArray()) { |
1145 | 0 | for (int i = 0; i < userProperties.arrayGetLength(); i++) { |
1146 | 0 | Object property = userProperties.arrayGet(i); |
1147 | 0 | if (property.isDict()) { |
1148 | 0 | Attribute *attribute = Attribute::parseUserProperty(property.getDict()); |
1149 | 0 | if (attribute && attribute->isOk()) { |
1150 | 0 | appendAttribute(attribute); |
1151 | 0 | } else { |
1152 | 0 | error(errSyntaxWarning, -1, "Item in P is invalid"); |
1153 | 0 | delete attribute; |
1154 | 0 | } |
1155 | 0 | } else { |
1156 | 0 | error(errSyntaxWarning, -1, "Item in P is wrong type ({0:s})", property.getTypeName()); |
1157 | 0 | } |
1158 | 0 | } |
1159 | 0 | } |
1160 | 13.5k | } else if (owner.isName()) { |
1161 | | // In this case /P contains standard attributes. |
1162 | | // Check first if the owner is a valid standard one. |
1163 | 12.4k | const std::string &ownerName = owner.getNameString(); |
1164 | 12.4k | Attribute::Owner ownerValue = nameToOwner(ownerName); |
1165 | 12.4k | if (ownerValue != Attribute::UnknownOwner) { |
1166 | | // Iterate over the entries of the "attributes" dictionary. |
1167 | | // The /O entry (owner) is skipped. |
1168 | 58.8k | for (int i = 0; i < attributes->getLength(); i++) { |
1169 | 46.8k | const std::string &key = attributes->getKey(i); |
1170 | 46.8k | if (key != "O") { |
1171 | 34.7k | Attribute::Type t = Attribute::getTypeForName(key, this); |
1172 | | |
1173 | | // Check if the attribute is already defined. |
1174 | 34.7k | if (keepExisting) { |
1175 | 26.1k | bool exists = false; |
1176 | 45.6k | for (unsigned j = 0; j < getNumAttributes(); j++) { |
1177 | 23.8k | if (getAttribute(j)->getType() == t) { |
1178 | 4.31k | exists = true; |
1179 | 4.31k | break; |
1180 | 4.31k | } |
1181 | 23.8k | } |
1182 | 26.1k | if (exists) { |
1183 | 4.31k | continue; |
1184 | 4.31k | } |
1185 | 26.1k | } |
1186 | | |
1187 | 30.4k | if (t != Attribute::Unknown) { |
1188 | 21.9k | Object value = attributes->getVal(i); |
1189 | 21.9k | bool typeCheckOk = true; |
1190 | 21.9k | auto *attribute = new Attribute(t, &value); |
1191 | | |
1192 | 21.9k | if (attribute->isOk() && (typeCheckOk = attribute->checkType(this))) { |
1193 | 20.9k | appendAttribute(attribute); |
1194 | 20.9k | } else { |
1195 | | // It is not needed to free "value", the Attribute instance |
1196 | | // owns the contents, so deleting "attribute" is enough. |
1197 | 1.02k | if (!typeCheckOk) { |
1198 | 1.02k | error(errSyntaxWarning, -1, "Attribute {0:s} value is of wrong type ({1:s})", attribute->getTypeName(), attribute->getValue()->getTypeName()); |
1199 | 1.02k | } |
1200 | 1.02k | delete attribute; |
1201 | 1.02k | } |
1202 | 21.9k | } else { |
1203 | 8.48k | error(errSyntaxWarning, -1, "Wrong Attribute '{0:r}' in element {1:s}", &key, getTypeName()); |
1204 | 8.48k | } |
1205 | 30.4k | } |
1206 | 46.8k | } |
1207 | 11.9k | } else { |
1208 | 484 | error(errSyntaxWarning, -1, "O object is invalid value ({0:r})", &ownerName); |
1209 | 484 | } |
1210 | 12.4k | } else if (!owner.isNull()) { |
1211 | 76 | error(errSyntaxWarning, -1, "O is wrong type ({0:s})", owner.getTypeName()); |
1212 | 76 | } |
1213 | 13.5k | } |