Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/poppler/StructElement.h
Line
Count
Source
1
//========================================================================
2
//
3
// StructElement.h
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, 2018, 2019, 2021, 2023-2026 Albert Astals Cid <aacid@kde.org>
10
// Copyright 2018 Adam Reichold <adam.reichold@t-online.de>
11
// Copyright 2021, 2023 Adrian Johnson <ajohnson@redneon.com>
12
// Copyright 2025, 2026 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
13
//
14
//========================================================================
15
16
#ifndef STRUCTELEMENT_H
17
#define STRUCTELEMENT_H
18
19
#include "goo/GooString.h"
20
#include "MarkedContentOutputDev.h"
21
#include "Object.h"
22
#include "poppler_private_export.h"
23
#include <vector>
24
25
class Dict;
26
class StructElement;
27
class StructTreeRoot;
28
29
class POPPLER_PRIVATE_EXPORT Attribute
30
{
31
public:
32
    enum Type
33
    {
34
        Unknown = 0, // Uninitialized, parsing error, etc.
35
        UserProperty, // User defined attribute (i.e. non-standard)
36
37
        // Common standard attributes
38
        Placement,
39
        WritingMode,
40
        BackgroundColor,
41
        BorderColor,
42
        BorderStyle,
43
        BorderThickness,
44
        Color,
45
        Padding,
46
47
        // Block element standard attributes
48
        SpaceBefore,
49
        SpaceAfter,
50
        StartIndent,
51
        EndIndent,
52
        TextIndent,
53
        TextAlign,
54
        BBox,
55
        Width,
56
        Height,
57
        BlockAlign,
58
        InlineAlign,
59
        TBorderStyle,
60
        TPadding,
61
62
        // Inline element standard attributes
63
        BaselineShift,
64
        LineHeight,
65
        TextDecorationColor,
66
        TextDecorationThickness,
67
        TextDecorationType,
68
        RubyAlign,
69
        RubyPosition,
70
        GlyphOrientationVertical,
71
72
        // Column-only standard attributes
73
        ColumnCount,
74
        ColumnGap,
75
        ColumnWidths,
76
77
        // List-only standard attributes
78
        ListNumbering,
79
80
        // PrintField-only standard attributes
81
        Role,
82
        checked,
83
        Desc,
84
85
        // Table-only standard attributes
86
        RowSpan,
87
        ColSpan,
88
        Headers,
89
        Scope,
90
        Summary,
91
    };
92
93
    enum Owner
94
    {
95
        UnknownOwner = 0,
96
        // User-defined attributes
97
        UserProperties,
98
        // Standard attributes
99
        Layout,
100
        List,
101
        PrintField,
102
        Table,
103
        // Translation to other formats
104
        XML_1_00,
105
        HTML_3_20,
106
        HTML_4_01,
107
        OEB_1_00,
108
        RTF_1_05,
109
        CSS_1_00,
110
        CSS_2_00,
111
    };
112
113
    // Creates a standard attribute. The name is predefined, and the
114
    // value is type-checked to conform to the PDF specification.
115
    Attribute(Type type, Object *value);
116
117
    // Creates an UserProperty attribute, with an arbitrary name and value.
118
    Attribute(GooString &&name, Object *value);
119
120
0
    bool isOk() const { return type != Unknown; }
121
122
    // Name, type and value can be set only on construction.
123
0
    Type getType() const { return type; }
124
0
    Owner getOwner() const { return owner; }
125
    const char *getTypeName() const;
126
    const char *getOwnerName() const;
127
0
    const Object *getValue() const { return &value; }
128
    static Object *getDefaultValue(Type type);
129
130
0
    const char *getName() const { return (type == UserProperty ? name.c_str() : getTypeName()); }
131
132
    // The revision is optional, and defaults to zero.
133
0
    unsigned int getRevision() const { return revision; }
134
0
    void setRevision(unsigned int revisionA) { revision = revisionA; }
135
136
    // Hidden elements should not be displayed by the user agent
137
0
    bool isHidden() const { return hidden; }
138
0
    void setHidden(bool hiddenA) { hidden = hiddenA; }
139
140
    // The formatted value may be in the PDF, or be left undefined (nullptr).
141
    // In the later case the user agent should provide a default representation.
142
0
    const char *getFormattedValue() const { return formatted ? formatted->c_str() : nullptr; }
143
    void setFormattedValue(const std::optional<std::string> &formattedA);
144
145
    ~Attribute();
146
147
private:
148
    Type type;
149
    Owner owner;
150
    unsigned int revision;
151
    GooString name;
152
    Object value;
153
    bool hidden;
154
    std::optional<std::string> formatted;
155
156
    bool checkType(StructElement *element = nullptr);
157
    static Type getTypeForName(const std::string &name, StructElement *element = nullptr);
158
    static Attribute *parseUserProperty(Dict *property);
159
160
    friend class StructElement;
161
};
162
163
class POPPLER_PRIVATE_EXPORT StructElement
164
{
165
public:
166
    enum Type
167
    {
168
        Unknown = 0,
169
        MCID, // MCID reference, used internally
170
        OBJR, // Object reference, used internally
171
172
        Document,
173
        Part,
174
        Art,
175
        Sect,
176
        Div, // Structural elements
177
178
        Span,
179
        Quote,
180
        Note,
181
        Reference,
182
        BibEntry, // Inline elements
183
        Code,
184
        Link,
185
        Annot,
186
        BlockQuote,
187
        Caption,
188
        NonStruct,
189
        TOC,
190
        TOCI,
191
        Index,
192
        Private,
193
194
        P,
195
        H,
196
        H1,
197
        H2,
198
        H3,
199
        H4,
200
        H5,
201
        H6, // Paragraph-like
202
203
        L,
204
        LI,
205
        Lbl,
206
        LBody, // List elements
207
208
        Table,
209
        TR,
210
        TH,
211
        TD,
212
        THead,
213
        TFoot,
214
        TBody, // Table elements
215
216
        Ruby,
217
        RB,
218
        RT,
219
        RP, // Ruby text elements
220
        Warichu,
221
        WT,
222
        WP,
223
224
        Figure,
225
        Formula,
226
        Form, // Illustration-like elements
227
    };
228
229
    static const Ref InvalidRef;
230
231
    const char *getTypeName() const;
232
0
    Type getType() const { return type; }
233
0
    bool isOk() const { return type != Unknown; }
234
    bool isBlock() const;
235
    bool isInline() const;
236
    bool isGrouping() const;
237
238
0
    bool isContent() const { return (type == MCID) || isObjectRef(); }
239
0
    bool isObjectRef() const { return (type == OBJR && c->ref != Ref::INVALID()); }
240
241
0
    int getMCID() const { return c->mcid; }
242
0
    Ref getObjectRef() const { return c->ref; }
243
0
    Ref getParentRef() const { return isContent() ? parent->getParentRef() : s->parentRef; }
244
0
    StructElement *getParent() const { return parent; } // returns NULL if parent is StructTreeRoot
245
    bool hasPageRef() const;
246
    bool getPageRef(Ref &ref) const;
247
0
    bool hasStmRef() const { return stmRef.isRef(); }
248
    bool getStmRef(Ref &ref) const;
249
0
    StructTreeRoot *getStructTreeRoot() { return treeRoot; }
250
251
    // Optional element identifier.
252
0
    const GooString *getID() const { return isContent() ? nullptr : s->id.get(); }
253
254
    // Optional ISO language name, e.g. en_US
255
    GooString *getLanguage()
256
0
    {
257
0
        if (!isContent() && s->language) {
258
0
            return s->language.get();
259
0
        }
260
0
        return parent ? parent->getLanguage() : nullptr;
261
0
    }
262
    const GooString *getLanguage() const
263
0
    {
264
0
        if (!isContent() && s->language) {
265
0
            return s->language.get();
266
0
        }
267
0
        return parent ? parent->getLanguage() : nullptr;
268
0
    }
269
270
    // Optional revision number, defaults to zero.
271
0
    unsigned int getRevision() const { return isContent() ? 0 : s->revision; }
272
    void setRevision(unsigned int revision) const
273
0
    {
274
0
        if (isContent()) {
275
0
            s->revision = revision;
276
0
        }
277
0
    }
278
279
    // Optional element title, in human-readable form.
280
0
    const GooString *getTitle() const { return isContent() ? nullptr : s->title.get(); }
281
282
    // Optional element expanded abbreviation text.
283
0
    const GooString *getExpandedAbbr() const { return isContent() ? nullptr : s->expandedAbbr.get(); }
284
285
0
    unsigned getNumChildren() const { return isContent() ? 0 : s->elements.size(); }
286
0
    const StructElement *getChild(int i) const { return isContent() ? nullptr : s->elements.at(i); }
287
288
    void appendChild(StructElement *element) const
289
0
    {
290
0
        if (!isContent() && element && element->isOk()) {
291
0
            s->elements.push_back(element);
292
0
        }
293
0
    }
294
295
0
    unsigned getNumAttributes() const { return isContent() ? 0 : s->attributes.size(); }
296
0
    const Attribute *getAttribute(int i) const { return isContent() ? nullptr : s->attributes.at(i); }
297
0
    Attribute *getNonConstAttribute(int i) const { return isContent() ? nullptr : s->attributes.at(i); }
298
299
    void appendAttribute(Attribute *attribute) const
300
0
    {
301
0
        if (!isContent() && attribute) {
302
0
            s->attributes.push_back(attribute);
303
0
        }
304
0
    }
305
306
    const Attribute *findAttribute(Attribute::Type attributeType, bool inherit = false, Attribute::Owner owner = Attribute::UnknownOwner) const;
307
308
0
    const GooString *getAltText() const { return isContent() ? nullptr : s->altText.get(); }
309
310
0
    const GooString *getActualText() const { return isContent() ? nullptr : s->actualText.get(); }
311
312
    // Content text referenced by the element:
313
    //
314
    // - For MCID reference elements, this is just the text of the
315
    //   corresponding marked content object in the page stream, regardless
316
    //   of the setting of the "recursive" flag.
317
    // - For other elements, if the "recursive" flag is set, the text
318
    //   enclosed by *all* the child MCID reference elements of the subtree
319
    //   is returned. The text is assembled by traversing the leaf MCID
320
    //   reference elements in logical order.
321
    // - In any other case, the function returns nullptr.
322
    //
323
    // A new string is returned, and the ownership passed to the caller.
324
    //
325
0
    GooString *getText(bool recursive = true) const { return appendSubTreeText(nullptr, recursive); }
326
327
    TextSpanArray getTextSpans() const
328
0
    {
329
0
        if (!isContent()) {
330
0
            return TextSpanArray();
331
0
        }
332
0
        MarkedContentOutputDev mcdev(getMCID(), stmRef);
333
0
        return getTextSpansInternal(mcdev);
334
0
    }
335
336
    ~StructElement();
337
338
private:
339
    GooString *appendSubTreeText(GooString *string, bool recursive) const;
340
    const TextSpanArray &getTextSpansInternal(MarkedContentOutputDev &mcdev) const;
341
342
    using AttrPtrArray = std::vector<Attribute *>;
343
    using ElemPtrArray = std::vector<StructElement *>;
344
345
    struct StructData
346
    {
347
        Ref parentRef;
348
        std::unique_ptr<GooString> altText;
349
        std::unique_ptr<GooString> actualText;
350
        std::unique_ptr<GooString> id;
351
        std::unique_ptr<GooString> title;
352
        std::unique_ptr<GooString> expandedAbbr;
353
        std::unique_ptr<GooString> language;
354
        unsigned int revision = 0;
355
        ElemPtrArray elements;
356
        AttrPtrArray attributes;
357
358
        StructData();
359
        ~StructData();
360
361
        StructData(const StructData &) = delete;
362
        StructData &operator=(const StructData &) = delete;
363
    };
364
365
    // Data in content elements (MCID, MCR)
366
    struct ContentData
367
    {
368
        union {
369
            int mcid;
370
            Ref ref;
371
        };
372
373
0
        explicit ContentData(int mcidA) : mcid(mcidA) { }
374
0
        explicit ContentData(const Ref r) { ref = r; }
375
    };
376
377
    // Common data
378
    Type type;
379
    StructTreeRoot *treeRoot;
380
    StructElement *parent;
381
    mutable Object pageRef;
382
    Object stmRef;
383
384
    union {
385
        StructData *s;
386
        ContentData *c;
387
    };
388
389
    StructElement(Dict *elementDict, StructTreeRoot *treeRootA, StructElement *parentA, RefRecursionChecker &seen);
390
    StructElement(int mcid, StructTreeRoot *treeRootA, StructElement *parentA);
391
    StructElement(Ref ref, StructTreeRoot *treeRootA, StructElement *parentA);
392
393
    void parse(Dict *elementDict);
394
    StructElement *parseChild(const Object *ref, Object *childObj, RefRecursionChecker &seen);
395
    void parseChildren(Dict *element, RefRecursionChecker &seen);
396
    void parseAttributes(Dict *attributes, bool keepExisting = false);
397
398
    friend class StructTreeRoot;
399
};
400
401
#endif