Coverage Report

Created: 2026-09-28 08:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}