Coverage Report

Created: 2026-05-30 06:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/libqpdf/QPDFObjectHandle.cc
Line
Count
Source
1
#include <qpdf/QPDFObjectHandle_private.hh>
2
3
#include <qpdf/JSON_writer.hh>
4
#include <qpdf/Pipeline_private.hh>
5
#include <qpdf/Pl_Buffer.hh>
6
#include <qpdf/Pl_QPDFTokenizer.hh>
7
#include <qpdf/QIntC.hh>
8
#include <qpdf/QPDF.hh>
9
#include <qpdf/QPDFExc.hh>
10
#include <qpdf/QPDFLogger.hh>
11
#include <qpdf/QPDFMatrix.hh>
12
#include <qpdf/QPDFObject_private.hh>
13
#include <qpdf/QPDFPageObjectHelper.hh>
14
#include <qpdf/QPDFParser.hh>
15
#include <qpdf/QTC.hh>
16
#include <qpdf/Util.hh>
17
18
#include <array>
19
#include <cctype>
20
#include <climits>
21
#include <cstdlib>
22
#include <cstring>
23
#include <stdexcept>
24
25
using namespace std::literals;
26
using namespace qpdf;
27
28
using Parser = impl::Parser;
29
30
const Null Null::temp_;
31
32
BaseHandle::
33
operator QPDFObjGen() const
34
1.51M
{
35
1.51M
    return obj ? obj->getObjGen() : QPDFObjGen();
36
1.51M
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
274k
    supports_retry(supports_retry)
47
274k
{
48
274k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
274k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
274k
}
54
55
void
56
QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline)
57
0
{
58
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline);
59
0
}
60
61
bool
62
QPDFObjectHandle::StreamDataProvider::provideStreamData(
63
    QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
64
0
{
65
0
    return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_retry);
66
0
}
67
68
void
69
QPDFObjectHandle::StreamDataProvider::provideStreamData(
70
    int objid, int generation, Pipeline* pipeline)
71
0
{
72
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
73
0
}
74
75
bool
76
QPDFObjectHandle::StreamDataProvider::provideStreamData(
77
    int objid, int generation, Pipeline* pipeline, bool suppress_warnings, bool will_retry)
78
0
{
79
0
    throw std::logic_error("you must override provideStreamData -- see QPDFObjectHandle.hh");
80
0
    return false;
81
0
}
82
83
bool
84
QPDFObjectHandle::StreamDataProvider::supportsRetry()
85
7.49k
{
86
7.49k
    return supports_retry;
87
7.49k
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
7.49k
            containing_page(containing_page),
96
7.49k
            old_contents(old_contents)
97
7.49k
        {
98
7.49k
        }
99
7.49k
        ~CoalesceProvider() override = default;
100
        void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override;
101
102
      private:
103
        QPDFObjectHandle containing_page;
104
        QPDFObjectHandle old_contents;
105
    };
106
} // namespace
107
108
void
109
CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p)
110
7.49k
{
111
7.49k
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
7.49k
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
7.49k
    std::string all_description;
114
7.49k
    old_contents.pipeContentStreams(p, description, all_description);
115
7.49k
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
24.3k
{
120
24.3k
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
87.7k
{
125
87.7k
    pipeline = p;
126
87.7k
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
6.41M
{
131
6.41M
    if (!pipeline) {
132
0
        return;
133
0
    }
134
6.41M
    if (len) {
135
6.39M
        pipeline->write(data, len);
136
6.39M
    }
137
6.41M
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
789k
{
142
789k
    write(str.c_str(), str.length());
143
789k
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
3.42M
{
148
3.42M
    std::string const& value = token.getRawValue();
149
3.42M
    write(value.c_str(), value.length());
150
3.42M
}
151
152
void
153
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle)
154
0
{
155
0
    throw std::logic_error("You must override one of the handleObject methods in ParserCallbacks");
156
0
}
157
158
void
159
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle oh, size_t, size_t)
160
0
{
161
    // This version of handleObject was added in qpdf 9. If the developer did not override it, fall
162
    // back to the older interface.
163
0
    handleObject(oh);
164
0
}
165
166
void
167
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
168
0
{
169
    // Ignore by default; overriding this is optional.
170
0
}
171
172
void
173
QPDFObjectHandle::ParserCallbacks::terminateParsing()
174
0
{
175
0
    throw TerminateParsing();
176
0
}
177
178
std::pair<bool, bool>
179
Name::analyzeJSONEncoding(const std::string& name)
180
763k
{
181
763k
    int tail = 0;       // Number of continuation characters expected.
182
763k
    bool tail2 = false; // Potential overlong 3 octet utf-8.
183
763k
    bool tail3 = false; // potential overlong 4 octet
184
763k
    bool needs_escaping = false;
185
16.2M
    for (auto const& it: name) {
186
16.2M
        auto c = static_cast<unsigned char>(it);
187
16.2M
        if (tail) {
188
17.8k
            if ((c & 0xc0) != 0x80) {
189
8.71k
                return {false, false};
190
8.71k
            }
191
9.12k
            if (tail2) {
192
794
                if ((c & 0xe0) == 0x80) {
193
323
                    return {false, false};
194
323
                }
195
471
                tail2 = false;
196
8.33k
            } else if (tail3) {
197
574
                if ((c & 0xf0) == 0x80) {
198
345
                    return {false, false};
199
345
                }
200
229
                tail3 = false;
201
229
            }
202
8.45k
            tail--;
203
16.1M
        } else if (c < 0x80) {
204
16.1M
            if (!needs_escaping) {
205
5.16M
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
206
5.16M
            }
207
16.1M
        } else if ((c & 0xe0) == 0xc0) {
208
6.50k
            if ((c & 0xfe) == 0xc0) {
209
588
                return {false, false};
210
588
            }
211
5.91k
            tail = 1;
212
37.2k
        } else if ((c & 0xf0) == 0xe0) {
213
5.59k
            tail2 = (c == 0xe0);
214
5.59k
            tail = 2;
215
31.6k
        } else if ((c & 0xf8) == 0xf0) {
216
3.32k
            tail3 = (c == 0xf0);
217
3.32k
            tail = 3;
218
28.3k
        } else {
219
28.3k
            return {false, false};
220
28.3k
        }
221
16.2M
    }
222
724k
    return {tail == 0, !needs_escaping};
223
763k
}
224
225
std::string
226
Name::normalize(std::string const& name)
227
20.4M
{
228
20.4M
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
20.4M
    std::string result;
232
20.4M
    result += name.at(0);
233
344M
    for (size_t i = 1; i < name.length(); ++i) {
234
323M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
323M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
5.20M
            result += "#";
239
318M
        } else if (
240
318M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
239M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
239M
            result += util::hex_encode_char(ch);
243
239M
        } else {
244
78.7M
            result += ch;
245
78.7M
        }
246
323M
    }
247
20.4M
    return result;
248
20.4M
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
2.21M
{
253
2.21M
    switch (resolved_type_code()) {
254
0
    case ::ot_uninitialized:
255
0
        throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object");
256
0
        return {}; // does not return
257
0
    case ::ot_reserved:
258
0
        return QPDFObject::create<QPDF_Reserved>();
259
218k
    case ::ot_null:
260
218k
        return QPDFObject::create<QPDF_Null>();
261
5.33k
    case ::ot_boolean:
262
5.33k
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
443k
    case ::ot_integer:
264
443k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
130k
    case ::ot_real:
266
130k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
209k
    case ::ot_string:
268
209k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
572k
    case ::ot_name:
270
572k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
79.9k
    case ::ot_array:
272
79.9k
        {
273
79.9k
            auto const& a = std::get<QPDF_Array>(obj->value);
274
79.9k
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
79.9k
            } else {
277
79.9k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
79.9k
                if (a.sp) {
279
1.04k
                    QPDF_Array result;
280
1.04k
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
1.04k
                    result.sp->size = a.sp->size;
282
31.6k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
31.6k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
31.6k
                    }
285
1.04k
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
78.8k
                } else {
287
78.8k
                    std::vector<QPDFObjectHandle> result;
288
78.8k
                    result.reserve(a.elements.size());
289
1.81M
                    for (auto const& element: a.elements) {
290
1.81M
                        result.emplace_back(
291
1.81M
                            element ? (element.indirect() ? element : element.copy()) : element);
292
1.81M
                    }
293
78.8k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
78.8k
                }
295
79.9k
            }
296
79.9k
        }
297
556k
    case ::ot_dictionary:
298
556k
        {
299
556k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
556k
            if (shallow) {
301
459k
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
459k
            } else {
303
96.6k
                std::map<std::string, QPDFObjectHandle> new_items;
304
570k
                for (auto const& [key, val]: d.items) {
305
570k
                    new_items[key] = val.indirect() ? val : val.copy();
306
570k
                }
307
96.6k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
96.6k
            }
309
556k
        }
310
27
    case ::ot_stream:
311
27
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
312
27
        throw std::runtime_error("stream objects cannot be cloned");
313
0
        return {}; // does not return
314
0
    case ::ot_operator:
315
0
        return QPDFObject::create<QPDF_Operator>(std::get<QPDF_Operator>(obj->value).val);
316
0
    case ::ot_inlineimage:
317
0
        return QPDFObject::create<QPDF_InlineImage>(std::get<QPDF_InlineImage>(obj->value).val);
318
0
    case ::ot_unresolved:
319
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
320
0
        return {}; // does not return
321
0
    case ::ot_destroyed:
322
0
        throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF");
323
0
        return {}; // does not return
324
0
    case ::ot_reference:
325
0
        return referenced_object().obj_sp();
326
2.21M
    }
327
0
    return {}; // unreachable
328
2.21M
}
329
330
// This method determines structural equivalence up to a given depth.
331
// The default depth is 10.
332
//
333
// Nomenclature note: ISO 32000-2 Annex J uses the term "equal" for this
334
// strict recursive comparison (J.4.1). We use "equivalent_to" here to
335
// implement Annex J's "equality", distinguishing it from C++ shallow
336
// pointer equality.
337
//
338
// Implementation notes:
339
//
340
// (1) We deviate from Annex J by comparing raw streams only, without
341
// decoding.
342
//
343
// (2) Loop detection is expensive and is avoided. If either object has
344
// a cycle in its forward orbit, this implementation will return false.
345
346
bool
347
BaseHandle::equivalent_to(BaseHandle const& other, int depth) const
348
0
{
349
    // A. Identity, size & limit checks
350
0
    if (obj == other.obj) {
351
0
        return true;
352
0
    }
353
0
    if (depth < 0) {
354
0
        return false;
355
0
    }
356
0
    size_t size1 = size();
357
0
    size_t size2 = other.size();
358
0
    if (size1 != size2) {
359
0
        return false;
360
0
    }
361
    // B. Structural comparison
362
0
    qpdf_object_type_e t1 = resolved_type_code();
363
0
    qpdf_object_type_e t2 = other.resolved_type_code();
364
0
    if (t1 == ::ot_reference) {
365
0
        return referenced_object().equivalent_to(other, depth - 1);
366
0
    }
367
0
    if (t2 == ::ot_reference) {
368
0
        return equivalent_to(other.referenced_object(), depth - 1);
369
0
    }
370
0
    if (t1 != t2) {
371
0
        if ((t1 == ::ot_integer || t1 == ::ot_real) && (t2 == ::ot_integer || t2 == ::ot_real)) {
372
            // Numeric equivalence per Annex J
373
0
            return oh().getNumericValue() == other.oh().getNumericValue();
374
0
        }
375
        // normalize uninitialized and null
376
0
        return (t1 == ::ot_uninitialized && t2 == ::ot_null) ||
377
0
            (t2 == ::ot_uninitialized && t1 == ::ot_null);
378
0
    }
379
0
    switch (t1) {
380
0
    case ::ot_uninitialized:
381
0
    case ::ot_null:
382
0
        return true;
383
0
    case ::ot_boolean:
384
0
        return std::get<QPDF_Bool>(obj->value).val == std::get<QPDF_Bool>(other.obj->value).val;
385
0
    case ::ot_string:
386
0
        return std::get<QPDF_String>(obj->value).val == std::get<QPDF_String>(other.obj->value).val;
387
0
    case ::ot_name:
388
0
        return std::get<QPDF_Name>(obj->value).name == std::get<QPDF_Name>(other.obj->value).name;
389
0
    case ::ot_array:
390
0
        {
391
0
            auto const& a1 = std::get<QPDF_Array>(obj->value);
392
0
            auto const& a2 = std::get<QPDF_Array>(other.obj->value);
393
            // sizes size1, size2 were calculated above and checked to be equal
394
0
            if (!a1.sp && !a2.sp) {
395
0
                for (size_t i = 0; i < size1; ++i) {
396
0
                    if (!a1.elements[i].equivalent_to(a2.elements[i], depth - 1)) {
397
0
                        return false;
398
0
                    }
399
0
                }
400
0
                return true;
401
0
            }
402
            // at least one array is sparse
403
0
            auto get_item = [](QPDF_Array const& arr, size_t idx) -> BaseHandle const& {
404
0
                if (arr.sp) {
405
0
                    auto it = arr.sp->elements.find(idx);
406
0
                    if (it == arr.sp->elements.end()) {
407
0
                        static QPDFObjectHandle null_oh = Null();
408
0
                        return null_oh;
409
0
                    }
410
0
                    return it->second;
411
0
                }
412
0
                return arr.elements[idx];
413
0
            };
414
0
            for (size_t i = 0; i < size1; ++i) {
415
0
                if (!get_item(a1, i).equivalent_to(get_item(a2, i), depth - 1)) {
416
0
                    return false;
417
0
                }
418
0
            }
419
0
            return true;
420
0
        }
421
0
    case ::ot_dictionary:
422
0
        {
423
0
            auto const& map1 = std::get<QPDF_Dictionary>(obj->value).items;
424
0
            auto const& map2 = std::get<QPDF_Dictionary>(other.obj->value).items;
425
0
            auto it2 = map2.begin();
426
0
            auto end2 = map2.end();
427
0
            for (auto const& [key1, value1]: map1) {
428
0
                if (value1.null()) {
429
0
                    continue;
430
0
                }
431
0
                while (it2 != end2 && it2->second.null()) {
432
0
                    ++it2;
433
0
                }
434
0
                if (it2 == end2 || key1 != it2->first ||
435
0
                    !value1.equivalent_to(it2->second, depth - 1)) {
436
0
                    return false;
437
0
                }
438
0
                ++it2;
439
0
            }
440
0
            while (it2 != end2 && it2->second.null()) {
441
0
                ++it2;
442
0
            }
443
0
            return it2 == end2;
444
0
        }
445
0
    case ::ot_stream:
446
0
        {
447
0
            auto const& s1 = std::get<QPDF_Stream>(obj->value);
448
0
            auto const& s2 = std::get<QPDF_Stream>(other.obj->value);
449
0
            if (!s1.m->stream_dict.equivalent_to(s2.m->stream_dict, depth - 1)) {
450
0
                return false;
451
0
            }
452
0
            return s1.m->stream_data->view() == s2.m->stream_data->view();
453
0
        }
454
0
    case ::ot_operator:
455
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_operator");
456
0
    case ::ot_inlineimage:
457
0
        throw std::logic_error("Internal error in BaseHandle::equivalent_to: found ot_inlineimage");
458
0
    case ::ot_integer:
459
0
        return std::get<QPDF_Integer>(obj->value).val ==
460
0
            std::get<QPDF_Integer>(other.obj->value).val;
461
0
    case ::ot_real:
462
0
        return oh().getNumericValue() == other.oh().getNumericValue();
463
0
    case ::ot_unresolved: // cannot determine equivalence so return false
464
0
    case ::ot_reference:  // handled above
465
0
    case ::ot_destroyed:  // should not happen
466
0
    case ::ot_reserved:   // should not happen
467
0
        return false;
468
0
    }
469
0
    return false; // unreachable
470
0
}
471
472
std::string
473
BaseHandle::unparse() const
474
22.3M
{
475
22.3M
    switch (resolved_type_code()) {
476
0
    case ::ot_uninitialized:
477
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object");
478
0
        return ""; // does not return
479
0
    case ::ot_reserved:
480
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object");
481
0
        return ""; // does not return
482
1.19M
    case ::ot_null:
483
1.19M
        return "null";
484
49.9k
    case ::ot_boolean:
485
49.9k
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
3.51M
    case ::ot_integer:
487
3.51M
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
720k
    case ::ot_real:
489
720k
        return std::get<QPDF_Real>(obj->value).val;
490
722k
    case ::ot_string:
491
722k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
16.1M
    case ::ot_name:
493
16.1M
        return Name::normalize(std::get<QPDF_Name>(obj->value).name);
494
0
    case ::ot_array:
495
0
        {
496
0
            auto const& a = std::get<QPDF_Array>(obj->value);
497
0
            std::string result = "[ ";
498
0
            if (a.sp) {
499
0
                size_t next = 0;
500
0
                for (auto& [key, value]: a.sp->elements) {
501
0
                    for (size_t j = next; j < key; ++j) {
502
0
                        result += "null ";
503
0
                    }
504
0
                    result += value.unparse() + " ";
505
0
                    next = key + 1;
506
0
                }
507
0
                for (size_t j = next; j < a.sp->size; ++j) {
508
0
                    result += "null ";
509
0
                }
510
0
            } else {
511
0
                for (auto const& item: a.elements) {
512
0
                    result += item.unparse() + " ";
513
0
                }
514
0
            }
515
0
            result += "]";
516
0
            return result;
517
0
        }
518
0
    case ::ot_dictionary:
519
0
        {
520
0
            auto const& items = std::get<QPDF_Dictionary>(obj->value).items;
521
0
            std::string result = "<< ";
522
0
            for (auto& iter: items) {
523
0
                if (!iter.second.null()) {
524
0
                    result += Name::normalize(iter.first) + " " + iter.second.unparse() + " ";
525
0
                }
526
0
            }
527
0
            result += ">>";
528
0
            return result;
529
0
        }
530
0
    case ::ot_stream:
531
0
        return obj->og.unparse(' ') + " R";
532
0
    case ::ot_operator:
533
0
        return std::get<QPDF_Operator>(obj->value).val;
534
0
    case ::ot_inlineimage:
535
0
        return std::get<QPDF_InlineImage>(obj->value).val;
536
0
    case ::ot_unresolved:
537
0
        throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object");
538
0
        return ""; // does not return
539
0
    case ::ot_destroyed:
540
0
        throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF");
541
0
        return ""; // does not return
542
0
    case ::ot_reference:
543
0
        return obj->og.unparse(' ') + " R";
544
22.3M
    }
545
0
    return {}; // unreachable
546
22.3M
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
1.02M
{
551
1.02M
    switch (resolved_type_code()) {
552
0
    case ::ot_uninitialized:
553
0
        throw std::logic_error(
554
0
            "QPDFObjectHandle: attempting to get JSON from a uninitialized object");
555
0
        break; // unreachable
556
39.2k
    case ::ot_null:
557
39.2k
    case ::ot_operator:
558
39.2k
    case ::ot_inlineimage:
559
39.2k
        p << "null";
560
39.2k
        break;
561
8.15k
    case ::ot_boolean:
562
8.15k
        p << std::get<QPDF_Bool>(obj->value).val;
563
8.15k
        break;
564
214k
    case ::ot_integer:
565
214k
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
214k
        break;
567
107k
    case ::ot_real:
568
107k
        {
569
107k
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
107k
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
107k
            } else if (val.at(0) == '.') {
574
1.63k
                p << "0" << val;
575
106k
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
1.66k
                p << "-0." << val.substr(2);
577
104k
            } else {
578
104k
                p << val;
579
104k
            }
580
107k
            if (val.back() == '.') {
581
2.65k
                p << "0";
582
2.65k
            }
583
107k
        }
584
107k
        break;
585
138k
    case ::ot_string:
586
138k
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
587
138k
        break;
588
301k
    case ::ot_name:
589
301k
        {
590
301k
            auto const& n = std::get<QPDF_Name>(obj->value);
591
            // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When
592
            // updating this method make sure QPDF_Dictionary is also update.
593
301k
            if (json_version == 1) {
594
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
595
301k
            } else {
596
301k
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
597
282k
                    if (res.second) {
598
276k
                        p << "\"" << n.name << "\"";
599
276k
                    } else {
600
6.43k
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
601
6.43k
                    }
602
282k
                } else {
603
19.0k
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
19.0k
                }
605
301k
            }
606
301k
        }
607
301k
        break;
608
96.6k
    case ::ot_array:
609
96.6k
        {
610
96.6k
            auto const& a = std::get<QPDF_Array>(obj->value);
611
96.6k
            p.writeStart('[');
612
96.6k
            if (a.sp) {
613
735
                size_t next = 0;
614
11.6k
                for (auto& [key, value]: a.sp->elements) {
615
72.7k
                    for (size_t j = next; j < key; ++j) {
616
61.0k
                        p.writeNext() << "null";
617
61.0k
                    }
618
11.6k
                    p.writeNext();
619
11.6k
                    auto item_og = value.id_gen();
620
11.6k
                    if (item_og.isIndirect()) {
621
1.23k
                        p << "\"" << item_og.unparse(' ') << " R\"";
622
10.4k
                    } else {
623
10.4k
                        value.write_json(json_version, p);
624
10.4k
                    }
625
11.6k
                    next = key + 1;
626
11.6k
                }
627
21.5k
                for (size_t j = next; j < a.sp->size; ++j) {
628
20.7k
                    p.writeNext() << "null";
629
20.7k
                }
630
95.9k
            } else {
631
1.47M
                for (auto const& item: a.elements) {
632
1.47M
                    p.writeNext();
633
1.47M
                    auto item_og = item.id_gen();
634
1.47M
                    if (item_og.isIndirect()) {
635
887k
                        p << "\"" << item_og.unparse(' ') << " R\"";
636
887k
                    } else {
637
590k
                        item.write_json(json_version, p);
638
590k
                    }
639
1.47M
                }
640
95.9k
            }
641
96.6k
            p.writeEnd(']');
642
96.6k
        }
643
96.6k
        break;
644
122k
    case ::ot_dictionary:
645
122k
        {
646
122k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
647
122k
            p.writeStart('{');
648
532k
            for (auto& iter: d.items) {
649
532k
                if (!iter.second.null()) {
650
461k
                    p.writeNext();
651
461k
                    if (json_version == 1) {
652
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
653
0
                          << "\": ";
654
461k
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
655
440k
                        if (res.second) {
656
433k
                            p << "\"" << iter.first << "\": ";
657
433k
                        } else {
658
7.03k
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
659
7.03k
                        }
660
440k
                    } else {
661
21.1k
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
21.1k
                          << "\": ";
663
21.1k
                    }
664
461k
                    iter.second.writeJSON(json_version, p);
665
461k
                }
666
532k
            }
667
122k
            p.writeEnd('}');
668
122k
        }
669
122k
        break;
670
0
    case ::ot_stream:
671
0
        std::get<QPDF_Stream>(obj->value).m->stream_dict.writeJSON(json_version, p);
672
0
        break;
673
0
    case ::ot_reference:
674
0
        p << "\"" << obj->og.unparse(' ') << " R\"";
675
0
        break;
676
0
    default:
677
0
        throw std::logic_error("attempted to write an unsuitable object as JSON");
678
1.02M
    }
679
1.02M
}
680
681
void
682
BaseHandle::disconnect(bool only_direct)
683
44.8M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
44.8M
    if (only_direct && indirect()) {
686
3.14M
        return;
687
3.14M
    }
688
689
41.6M
    switch (raw_type_code()) {
690
1.12M
    case ::ot_array:
691
1.12M
        {
692
1.12M
            auto& a = std::get<QPDF_Array>(obj->value);
693
1.12M
            if (a.sp) {
694
13.8M
                for (auto& item: a.sp->elements) {
695
13.8M
                    item.second.disconnect();
696
13.8M
                }
697
1.12M
            } else {
698
17.6M
                for (auto& oh: a.elements) {
699
17.6M
                    oh.disconnect();
700
17.6M
                }
701
1.12M
            }
702
1.12M
        }
703
1.12M
        break;
704
1.98M
    case ::ot_dictionary:
705
7.98M
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
7.98M
            iter.second.disconnect();
707
7.98M
        }
708
1.98M
        break;
709
414k
    case ::ot_stream:
710
414k
        {
711
414k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
414k
            s.m->stream_provider = nullptr;
713
414k
            s.m->stream_dict.disconnect();
714
414k
        }
715
414k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
38.1M
    default:
719
38.1M
        break;
720
41.6M
    }
721
41.6M
    obj->qpdf = nullptr;
722
41.6M
    obj->og = QPDFObjGen();
723
41.6M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
13.5k
{
728
13.5k
    return obj == rhs.obj;
729
13.5k
}
730
731
qpdf_object_type_e
732
QPDFObjectHandle::getTypeCode() const
733
24.1M
{
734
24.1M
    return type_code();
735
24.1M
}
736
737
char const*
738
BaseHandle::type_name() const
739
85.2k
{
740
85.2k
    static constexpr std::array<char const*, 16> tn{
741
85.2k
        "uninitialized",
742
85.2k
        "reserved",
743
85.2k
        "null",
744
85.2k
        "boolean",
745
85.2k
        "integer",
746
85.2k
        "real",
747
85.2k
        "string",
748
85.2k
        "name",
749
85.2k
        "array",
750
85.2k
        "dictionary",
751
85.2k
        "stream",
752
85.2k
        "operator",
753
85.2k
        "inline-image",
754
85.2k
        "unresolved",
755
85.2k
        "destroyed",
756
85.2k
        "reference"};
757
85.2k
    return tn[type_code()];
758
85.2k
}
759
760
char const*
761
QPDFObjectHandle::getTypeName() const
762
0
{
763
0
    return type_name();
764
0
}
765
766
bool
767
QPDFObjectHandle::isDestroyed() const
768
0
{
769
0
    return type_code() == ::ot_destroyed;
770
0
}
771
772
bool
773
QPDFObjectHandle::isBool() const
774
665k
{
775
665k
    return type_code() == ::ot_boolean;
776
665k
}
777
778
bool
779
QPDFObjectHandle::isDirectNull() const
780
0
{
781
    // Don't call dereference() -- this is a const method, and we know
782
    // objid == 0, so there's nothing to resolve.
783
0
    return !indirect() && raw_type_code() == ::ot_null;
784
0
}
785
786
bool
787
QPDFObjectHandle::isNull() const
788
405k
{
789
405k
    return type_code() == ::ot_null;
790
405k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
3.71M
{
795
3.71M
    return type_code() == ::ot_integer;
796
3.71M
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
995k
{
801
995k
    return type_code() == ::ot_real;
802
995k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
853k
{
807
853k
    return (isInteger() || isReal());
808
853k
}
809
810
double
811
QPDFObjectHandle::getNumericValue() const
812
247k
{
813
247k
    if (isInteger()) {
814
30.5k
        return static_cast<double>(getIntValue());
815
217k
    } else if (isReal()) {
816
217k
        return atof(getRealValue().c_str());
817
217k
    } else {
818
0
        typeWarning("number", "returning 0");
819
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
820
0
        return 0;
821
0
    }
822
247k
}
823
824
bool
825
QPDFObjectHandle::getValueAsNumber(double& value) const
826
248k
{
827
248k
    if (!isNumber()) {
828
486
        return false;
829
486
    }
830
247k
    value = getNumericValue();
831
247k
    return true;
832
248k
}
833
834
bool
835
QPDFObjectHandle::isName() const
836
856k
{
837
856k
    return type_code() == ::ot_name;
838
856k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
353k
{
843
353k
    return type_code() == ::ot_string;
844
353k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
15.0M
{
849
15.0M
    return type_code() == ::ot_operator;
850
15.0M
}
851
852
bool
853
QPDFObjectHandle::isInlineImage() const
854
0
{
855
0
    return type_code() == ::ot_inlineimage;
856
0
}
857
858
bool
859
QPDFObjectHandle::isArray() const
860
10.6M
{
861
10.6M
    return type_code() == ::ot_array;
862
10.6M
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
50.6M
{
867
50.6M
    return type_code() == ::ot_dictionary;
868
50.6M
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
30.0M
{
873
30.0M
    return type_code() == ::ot_stream;
874
30.0M
}
875
876
bool
877
QPDFObjectHandle::isReserved() const
878
0
{
879
0
    return type_code() == ::ot_reserved;
880
0
}
881
882
bool
883
QPDFObjectHandle::isScalar() const
884
372k
{
885
372k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
372k
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
500k
{
891
500k
    return Name(*this) == name;
892
500k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
15.7M
{
897
15.7M
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
769k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
15.7M
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
1.53M
{
904
1.53M
    if (auto stream = as_stream()) {
905
442k
        return stream && (type.empty() || stream.Type() == type) &&
906
162k
            (subtype.empty() || stream.Subtype() == subtype);
907
442k
    }
908
1.09M
    return false;
909
1.53M
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
5.79k
{
916
5.79k
    if (auto boolean = as<QPDF_Bool>()) {
917
5.79k
        return boolean->val;
918
5.79k
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
5.79k
}
924
925
bool
926
QPDFObjectHandle::getValueAsBool(bool& value) const
927
0
{
928
0
    if (auto boolean = as<QPDF_Bool>()) {
929
0
        value = boolean->val;
930
0
        return true;
931
0
    }
932
0
    return false;
933
0
}
934
935
// Integer methods
936
937
Integer::Integer(long long value) :
938
78.0k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
78.0k
{
940
78.0k
}
941
942
QPDFObjectHandle
943
QPDFObjectHandle::newInteger(long long value)
944
2.27M
{
945
2.27M
    return {QPDFObject::create<QPDF_Integer>(value)};
946
2.27M
}
947
948
int64_t
949
Integer::value() const
950
2.40M
{
951
2.40M
    auto* i = as<QPDF_Integer>();
952
2.40M
    if (!i) {
953
11.7k
        throw invalid_error("Integer");
954
11.7k
    }
955
2.38M
    return i->val;
956
2.40M
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
1.10M
{
961
1.10M
    if (auto const integer = Integer(*this)) {
962
1.10M
        return integer;
963
1.10M
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
1.10M
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
75.5k
{
972
75.5k
    if (auto const integer = Integer(*this)) {
973
74.7k
        value = integer;
974
74.7k
        return true;
975
74.7k
    }
976
893
    return false;
977
75.5k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
462k
{
982
462k
    try {
983
462k
        return Integer(*this).value<int>();
984
462k
    } catch (std::invalid_argument&) {
985
224
        typeWarning("integer", "returning 0");
986
224
        return 0;
987
224
    }
988
462k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
18.9k
{
993
18.9k
    if (!isInteger()) {
994
410
        return false;
995
410
    }
996
18.5k
    value = getIntValueAsInt();
997
18.5k
    return true;
998
18.9k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
336k
{
1003
336k
    try {
1004
336k
        return Integer(*this).value<unsigned long long>();
1005
336k
    } catch (std::invalid_argument&) {
1006
7.08k
        typeWarning("integer", "returning 0");
1007
7.08k
        return 0;
1008
7.08k
    }
1009
336k
}
1010
1011
bool
1012
QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const
1013
0
{
1014
0
    if (!isInteger()) {
1015
0
        return false;
1016
0
    }
1017
0
    value = getUIntValue();
1018
0
    return true;
1019
0
}
1020
1021
unsigned int
1022
QPDFObjectHandle::getUIntValueAsUInt() const
1023
81.3k
{
1024
81.3k
    try {
1025
81.3k
        return Integer(*this).value<unsigned int>();
1026
81.3k
    } catch (std::invalid_argument&) {
1027
4.39k
        typeWarning("integer", "returning 0");
1028
4.39k
        return 0;
1029
4.39k
    }
1030
81.3k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
21.8k
{
1035
21.8k
    if (!isInteger()) {
1036
2.89k
        return false;
1037
2.89k
    }
1038
18.9k
    value = getUIntValueAsUInt();
1039
18.9k
    return true;
1040
21.8k
}
1041
1042
// Real accessors
1043
1044
std::string
1045
QPDFObjectHandle::getRealValue() const
1046
217k
{
1047
217k
    if (auto* real = as<QPDF_Real>()) {
1048
217k
        return real->val;
1049
217k
    }
1050
0
    typeWarning("real", "returning 0.0");
1051
0
    return "0.0";
1052
217k
}
1053
1054
bool
1055
QPDFObjectHandle::getValueAsReal(std::string& value) const
1056
0
{
1057
0
    if (auto* real = as<QPDF_Real>()) {
1058
0
        value = real->val;
1059
0
        return true;
1060
0
    }
1061
0
    return false;
1062
0
}
1063
1064
// Name methods
1065
1066
QPDFObjectHandle
1067
QPDFObjectHandle::newName(std::string const& name)
1068
43.4k
{
1069
43.4k
    return {QPDFObject::create<QPDF_Name>(name)};
1070
43.4k
}
1071
1072
Name::Name(std::string const& name) :
1073
0
    BaseHandle(QPDFObject::create<QPDF_Name>(name))
1074
0
{
1075
0
}
1076
1077
Name::Name(std::string&& name) :
1078
225k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
225k
{
1080
225k
}
1081
1082
std::string const&
1083
Name::value() const
1084
2.84M
{
1085
2.84M
    auto* n = as<QPDF_Name>();
1086
2.84M
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
2.84M
    return n->name;
1090
2.84M
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
187k
{
1095
187k
    if (auto* name = as<QPDF_Name>()) {
1096
183k
        return name->name;
1097
183k
    }
1098
4.33k
    typeWarning("name", "returning dummy name");
1099
4.33k
    return "/QPDFFakeName";
1100
187k
}
1101
1102
bool
1103
QPDFObjectHandle::getValueAsName(std::string& value) const
1104
0
{
1105
0
    if (auto* name = as<QPDF_Name>()) {
1106
0
        value = name->name;
1107
0
        return true;
1108
0
    }
1109
0
    return false;
1110
0
}
1111
1112
// String methods
1113
1114
QPDFObjectHandle
1115
QPDFObjectHandle::newString(std::string const& str)
1116
153k
{
1117
153k
    return {QPDFObject::create<QPDF_String>(str)};
1118
153k
}
1119
1120
QPDFObjectHandle
1121
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1122
17.4k
{
1123
17.4k
    return {String::utf16(utf8_str).obj_sp()};
1124
17.4k
}
1125
1126
String::String(std::string const& str) :
1127
7.26k
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1128
7.26k
{
1129
7.26k
}
1130
1131
String::String(std::string&& str) :
1132
10.1k
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1133
10.1k
{
1134
10.1k
}
1135
1136
String
1137
String::utf16(std::string const& utf8_str)
1138
17.4k
{
1139
17.4k
    std::string result;
1140
17.4k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1141
7.26k
        return String(result);
1142
7.26k
    }
1143
10.1k
    return String(QUtil::utf8_to_utf16(utf8_str));
1144
17.4k
}
1145
1146
std::string const&
1147
String::value() const
1148
163k
{
1149
163k
    auto* s = as<QPDF_String>();
1150
163k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
163k
    return s->val;
1154
163k
}
1155
1156
std::string
1157
String::utf8_value() const
1158
1.55M
{
1159
1.55M
    auto* s = as<QPDF_String>();
1160
1.55M
    if (!s) {
1161
0
        throw invalid_error("String");
1162
0
    }
1163
1.55M
    if (util::is_utf16(s->val)) {
1164
105k
        return QUtil::utf16_to_utf8(s->val);
1165
105k
    }
1166
1.44M
    if (util::is_explicit_utf8(s->val)) {
1167
        // PDF 2.0 allows UTF-8 strings when explicitly prefixed with the three-byte representation
1168
        // of U+FEFF.
1169
9.32k
        return s->val.substr(3);
1170
9.32k
    }
1171
1.43M
    return QUtil::pdf_doc_to_utf8(s->val);
1172
1.44M
}
1173
1174
std::string
1175
QPDFObjectHandle::getStringValue() const
1176
153k
{
1177
153k
    try {
1178
153k
        return String(obj).value();
1179
153k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
153k
}
1184
1185
bool
1186
QPDFObjectHandle::getValueAsString(std::string& value) const
1187
0
{
1188
0
    try {
1189
0
        value = String(obj).value();
1190
0
        return true;
1191
0
    } catch (std::invalid_argument&) {
1192
0
        return false;
1193
0
    }
1194
0
}
1195
1196
std::string
1197
QPDFObjectHandle::getUTF8Value() const
1198
1.46M
{
1199
1.46M
    try {
1200
1.46M
        return String(obj).utf8_value();
1201
1.46M
    } catch (std::invalid_argument&) {
1202
0
        typeWarning("string", "returning empty string");
1203
0
        return {};
1204
0
    }
1205
1.46M
}
1206
1207
bool
1208
QPDFObjectHandle::getValueAsUTF8(std::string& value) const
1209
0
{
1210
0
    try {
1211
0
        value = String(obj).utf8_value();
1212
0
        return true;
1213
0
    } catch (std::invalid_argument&) {
1214
0
        return false;
1215
0
    }
1216
0
}
1217
1218
// Operator and Inline Image accessors
1219
1220
std::string
1221
QPDFObjectHandle::getOperatorValue() const
1222
14.5M
{
1223
14.5M
    if (auto* op = as<QPDF_Operator>()) {
1224
14.5M
        return op->val;
1225
14.5M
    }
1226
0
    typeWarning("operator", "returning fake value");
1227
0
    return "QPDFFAKE";
1228
14.5M
}
1229
1230
bool
1231
QPDFObjectHandle::getValueAsOperator(std::string& value) const
1232
0
{
1233
0
    if (auto* op = as<QPDF_Operator>()) {
1234
0
        value = op->val;
1235
0
        return true;
1236
0
    }
1237
0
    return false;
1238
0
}
1239
1240
std::string
1241
QPDFObjectHandle::getInlineImageValue() const
1242
0
{
1243
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1244
0
        return ii->val;
1245
0
    }
1246
0
    typeWarning("inlineimage", "returning empty data");
1247
0
    return {};
1248
0
}
1249
1250
bool
1251
QPDFObjectHandle::getValueAsInlineImage(std::string& value) const
1252
0
{
1253
0
    if (auto* ii = as<QPDF_InlineImage>()) {
1254
0
        value = ii->val;
1255
0
        return true;
1256
0
    }
1257
0
    return false;
1258
0
}
1259
1260
// Array accessors and mutators are in QPDF_Array.cc
1261
1262
QPDFObjectHandle::QPDFArrayItems
1263
QPDFObjectHandle::aitems()
1264
9.59k
{
1265
9.59k
    return *this;
1266
9.59k
}
1267
1268
// Dictionary accessors are in QPDF_Dictionary.cc
1269
1270
QPDFObjectHandle::QPDFDictItems
1271
QPDFObjectHandle::ditems()
1272
0
{
1273
0
    return {*this};
1274
0
}
1275
1276
// Array and Name accessors
1277
1278
bool
1279
QPDFObjectHandle::isOrHasName(std::string const& value) const
1280
169k
{
1281
169k
    if (isNameAndEquals(value)) {
1282
1.99k
        return true;
1283
167k
    } else if (isArray()) {
1284
238k
        for (auto& item: getArrayAsVector()) {
1285
238k
            if (item.isNameAndEquals(value)) {
1286
2.95k
                return true;
1287
2.95k
            }
1288
238k
        }
1289
64.6k
    }
1290
164k
    return false;
1291
169k
}
1292
1293
void
1294
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
1295
0
{
1296
0
    for (auto const& i1: as_dictionary()) {
1297
0
        for (auto& i2: i1.second.as_dictionary()) {
1298
0
            if (!i2.second.null() && !i2.second.isIndirect()) {
1299
0
                i2.second = owning_qpdf.makeIndirectObject(i2.second);
1300
0
            }
1301
0
        }
1302
0
    }
1303
0
}
1304
1305
void
1306
QPDFObjectHandle::mergeResources(
1307
    QPDFObjectHandle other, std::map<std::string, std::map<std::string, std::string>>* conflicts)
1308
48.0k
{
1309
48.0k
    if (!(isDictionary() && other.isDictionary())) {
1310
12.3k
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1311
12.3k
        return;
1312
12.3k
    }
1313
1314
35.7k
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1315
35.7k
                              std::map<QPDFObjGen, std::string>& og_to_name) {
1316
0
        for (auto const& [key, value]: dict.as_dictionary()) {
1317
0
            if (!value.null() && value.isIndirect()) {
1318
0
                og_to_name.insert_or_assign(value.getObjGen(), key);
1319
0
            }
1320
0
        }
1321
0
    };
1322
1323
    // This algorithm is described in comments in QPDFObjectHandle.hh
1324
    // above the declaration of mergeResources.
1325
70.4k
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1326
70.4k
        auto other_val = value1;
1327
70.4k
        if (hasKey(rtype)) {
1328
40.8k
            QPDFObjectHandle this_val = getKey(rtype);
1329
40.8k
            if (this_val.isDictionary() && other_val.isDictionary()) {
1330
28.0k
                if (this_val.isIndirect()) {
1331
                    // Do this even if there are no keys. Various places in the code call
1332
                    // mergeResources with resource dictionaries that contain empty subdictionaries
1333
                    // just to get this shallow copy functionality.
1334
5.90k
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1335
5.90k
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1336
5.90k
                }
1337
28.0k
                std::map<QPDFObjGen, std::string> og_to_name;
1338
28.0k
                std::set<std::string> rnames;
1339
28.0k
                int min_suffix = 1;
1340
28.0k
                bool initialized_maps = false;
1341
191k
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1342
191k
                    QPDFObjectHandle rval = value2;
1343
191k
                    if (!this_val.hasKey(key)) {
1344
57.1k
                        if (!rval.isIndirect()) {
1345
22.6k
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1346
22.6k
                            rval = rval.shallowCopy();
1347
22.6k
                        }
1348
57.1k
                        this_val.replaceKey(key, rval);
1349
134k
                    } else if (conflicts) {
1350
0
                        if (!initialized_maps) {
1351
0
                            make_og_to_name(this_val, og_to_name);
1352
0
                            rnames = this_val.getResourceNames();
1353
0
                            initialized_maps = true;
1354
0
                        }
1355
0
                        auto rval_og = rval.getObjGen();
1356
0
                        if (rval.isIndirect() && og_to_name.contains(rval_og)) {
1357
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
1358
0
                            auto new_key = og_to_name[rval_og];
1359
0
                            if (new_key != key) {
1360
0
                                (*conflicts)[rtype][key] = new_key;
1361
0
                            }
1362
0
                        } else {
1363
0
                            QTC::TC("qpdf", "QPDFObjectHandle merge generate");
1364
0
                            std::string new_key =
1365
0
                                getUniqueResourceName(key + "_", min_suffix, &rnames);
1366
0
                            (*conflicts)[rtype][key] = new_key;
1367
0
                            this_val.replaceKey(new_key, rval);
1368
0
                        }
1369
0
                    }
1370
191k
                }
1371
28.0k
            } else if (this_val.isArray() && other_val.isArray()) {
1372
4.79k
                std::set<std::string> scalars;
1373
173k
                for (auto this_item: this_val.aitems()) {
1374
173k
                    if (this_item.isScalar()) {
1375
171k
                        scalars.insert(this_item.unparse());
1376
171k
                    }
1377
173k
                }
1378
179k
                for (auto other_item: other_val.aitems()) {
1379
179k
                    if (other_item.isScalar()) {
1380
175k
                        if (!scalars.contains(other_item.unparse())) {
1381
73.6k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1382
73.6k
                            this_val.appendItem(other_item);
1383
101k
                        } else {
1384
101k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1385
101k
                        }
1386
175k
                    }
1387
179k
                }
1388
4.79k
            }
1389
40.8k
        } else {
1390
29.5k
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1391
29.5k
            replaceKey(rtype, other_val.shallowCopy());
1392
29.5k
        }
1393
70.4k
    }
1394
35.7k
}
1395
1396
std::set<std::string>
1397
QPDFObjectHandle::getResourceNames() const
1398
23.9k
{
1399
    // Return second-level dictionary keys
1400
23.9k
    std::set<std::string> result;
1401
69.6k
    for (auto const& item: as_dictionary(strict)) {
1402
564k
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1403
564k
            if (!val2.null()) {
1404
452k
                result.insert(key2);
1405
452k
            }
1406
564k
        }
1407
69.6k
    }
1408
23.9k
    return result;
1409
23.9k
}
1410
1411
std::string
1412
QPDFObjectHandle::getUniqueResourceName(
1413
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1414
23.9k
{
1415
23.9k
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1416
23.9k
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1417
24.0k
    while (min_suffix <= max_suffix) {
1418
24.0k
        std::string candidate = prefix + std::to_string(min_suffix);
1419
24.0k
        if (!names.contains(candidate)) {
1420
23.9k
            return candidate;
1421
23.9k
        }
1422
        // Increment after return; min_suffix should be the value
1423
        // used, not the next value.
1424
20
        ++min_suffix;
1425
20
    }
1426
    // This could only happen if there is a coding error.
1427
    // The number of candidates we test is more than the
1428
    // number of keys we're checking against.
1429
0
    throw std::logic_error(
1430
0
        "unable to find unconflicting name in QPDFObjectHandle::getUniqueResourceName");
1431
23.9k
}
1432
1433
// Dictionary mutators are in QPDF_Dictionary.cc
1434
1435
// Stream accessors are in QPDF_Stream.cc
1436
1437
std::map<std::string, QPDFObjectHandle>
1438
QPDFObjectHandle::getPageImages()
1439
0
{
1440
0
    return QPDFPageObjectHelper(*this).getImages();
1441
0
}
1442
1443
std::vector<QPDFObjectHandle>
1444
QPDFObjectHandle::arrayOrStreamToStreamArray(
1445
    std::string const& description, std::string& all_description)
1446
56.3k
{
1447
56.3k
    all_description = description;
1448
56.3k
    std::vector<QPDFObjectHandle> result;
1449
56.3k
    if (auto array = as_array(strict)) {
1450
13.4k
        int n_items = static_cast<int>(array.size());
1451
99.7k
        for (int i = 0; i < n_items; ++i) {
1452
86.3k
            QPDFObjectHandle item = array[i];
1453
86.3k
            if (item.isStream()) {
1454
35.6k
                result.emplace_back(item);
1455
50.6k
            } else {
1456
50.6k
                item.warn(
1457
50.6k
                    {qpdf_e_damaged_pdf,
1458
50.6k
                     "",
1459
50.6k
                     description + ": item index " + std::to_string(i) + " (from 0)",
1460
50.6k
                     0,
1461
50.6k
                     "ignoring non-stream in an array of streams"});
1462
50.6k
            }
1463
86.3k
        }
1464
42.8k
    } else if (isStream()) {
1465
12.6k
        result.emplace_back(*this);
1466
30.2k
    } else if (!null()) {
1467
1.27k
        warn(
1468
1.27k
            {qpdf_e_damaged_pdf,
1469
1.27k
             "",
1470
1.27k
             description,
1471
1.27k
             0,
1472
1.27k
             " object is supposed to be a stream or an array of streams but is neither"});
1473
1.27k
    }
1474
1475
56.3k
    bool first = true;
1476
56.3k
    for (auto const& item: result) {
1477
48.2k
        if (first) {
1478
24.6k
            first = false;
1479
24.6k
        } else {
1480
23.6k
            all_description += ",";
1481
23.6k
        }
1482
48.2k
        all_description += " stream " + item.getObjGen().unparse(' ');
1483
48.2k
    }
1484
1485
56.3k
    return result;
1486
56.3k
}
1487
1488
std::vector<QPDFObjectHandle>
1489
QPDFObjectHandle::getPageContents()
1490
11.4k
{
1491
11.4k
    std::string description = "page object " + getObjGen().unparse(' ');
1492
11.4k
    std::string all_description;
1493
11.4k
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1494
11.4k
}
1495
1496
void
1497
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1498
11.4k
{
1499
11.4k
    new_contents.assertStream();
1500
1501
11.4k
    std::vector<QPDFObjectHandle> content_streams;
1502
11.4k
    if (first) {
1503
5.77k
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1504
5.77k
        content_streams.push_back(new_contents);
1505
5.77k
    }
1506
11.4k
    for (auto const& iter: getPageContents()) {
1507
7.29k
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1508
7.29k
        content_streams.push_back(iter);
1509
7.29k
    }
1510
11.4k
    if (!first) {
1511
5.71k
        content_streams.push_back(new_contents);
1512
5.71k
    }
1513
1514
11.4k
    replaceKey("/Contents", newArray(content_streams));
1515
11.4k
}
1516
1517
void
1518
QPDFObjectHandle::rotatePage(int angle, bool relative)
1519
0
{
1520
0
    if ((angle % 90) != 0) {
1521
0
        throw std::runtime_error(
1522
0
            "QPDF::rotatePage called with an angle that is not a multiple of 90");
1523
0
    }
1524
0
    int new_angle = angle;
1525
0
    if (relative) {
1526
0
        int old_angle = 0;
1527
0
        QPDFObjectHandle cur_obj = *this;
1528
0
        QPDFObjGen::set visited;
1529
0
        while (visited.add(cur_obj)) {
1530
            // Don't get stuck in an infinite loop
1531
0
            if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) {
1532
0
                break;
1533
0
            } else if (cur_obj.getKey("/Parent").isDictionary()) {
1534
0
                cur_obj = cur_obj.getKey("/Parent");
1535
0
            } else {
1536
0
                break;
1537
0
            }
1538
0
        }
1539
0
        QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 0 : 1);
1540
0
        if ((old_angle % 90) != 0) {
1541
0
            old_angle = 0;
1542
0
        }
1543
0
        new_angle += old_angle;
1544
0
    }
1545
0
    new_angle = (new_angle + 360) % 360;
1546
    // Make this explicit even with new_angle == 0 since /Rotate can be inherited.
1547
0
    replaceKey("/Rotate", Integer(new_angle));
1548
0
}
1549
1550
void
1551
QPDFObjectHandle::coalesceContentStreams()
1552
37.3k
{
1553
37.3k
    QPDFObjectHandle contents = getKey("/Contents");
1554
37.3k
    if (contents.isStream()) {
1555
4.96k
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1556
4.96k
        return;
1557
32.3k
    } else if (!contents.isArray()) {
1558
        // /Contents is optional for pages, and some very damaged files may have pages that are
1559
        // invalid in other ways.
1560
24.8k
        return;
1561
24.8k
    }
1562
    // Should not be possible for a page object to not have an owning PDF unless it was manually
1563
    // constructed in some incorrect way. However, it can happen in a PDF file whose page structure
1564
    // is direct, which is against spec but still possible to hand construct, as in fuzz issue
1565
    // 27393.
1566
7.50k
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1567
1568
7.50k
    QPDFObjectHandle new_contents = newStream(&qpdf);
1569
7.50k
    replaceKey("/Contents", new_contents);
1570
1571
7.50k
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1572
7.50k
    new_contents.replaceStreamData(provider, newNull(), newNull());
1573
7.50k
}
1574
1575
std::string
1576
QPDFObjectHandle::unparse() const
1577
498k
{
1578
498k
    if (isIndirect()) {
1579
7.05k
        return getObjGen().unparse(' ') + " R";
1580
491k
    } else {
1581
491k
        return unparseResolved();
1582
491k
    }
1583
498k
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
22.3M
{
1588
22.3M
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
22.3M
    return BaseHandle::unparse();
1592
22.3M
}
1593
1594
std::string
1595
QPDFObjectHandle::unparseBinary() const
1596
0
{
1597
0
    if (auto str = as<QPDF_String>()) {
1598
0
        return str->unparse(true);
1599
0
    } else {
1600
0
        return unparse();
1601
0
    }
1602
0
}
1603
1604
JSON
1605
QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const
1606
32.1k
{
1607
32.1k
    if ((!dereference_indirect) && isIndirect()) {
1608
0
        return JSON::makeString(unparse());
1609
32.1k
    } else if (!obj) {
1610
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1611
32.1k
    } else {
1612
32.1k
        Pl_Buffer p{"json"};
1613
32.1k
        JSON::Writer jw{&p, 0};
1614
32.1k
        writeJSON(json_version, jw, dereference_indirect);
1615
32.1k
        p.finish();
1616
32.1k
        return JSON::parse(p.getString());
1617
32.1k
    }
1618
32.1k
}
1619
1620
void
1621
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1622
493k
{
1623
493k
    if (!dereference_indirect && isIndirect()) {
1624
65.8k
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1625
427k
    } else if (!obj) {
1626
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1627
427k
    } else {
1628
427k
        write_json(json_version, p);
1629
427k
    }
1630
493k
}
1631
1632
void
1633
QPDFObjectHandle::writeJSON(
1634
    int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const
1635
0
{
1636
0
    JSON::Writer jw{p, depth};
1637
0
    writeJSON(json_version, jw, dereference_indirect);
1638
0
}
1639
1640
QPDFObjectHandle
1641
QPDFObjectHandle::wrapInArray()
1642
0
{
1643
0
    if (isArray()) {
1644
0
        return *this;
1645
0
    }
1646
0
    QPDFObjectHandle result = QPDFObjectHandle::newArray();
1647
0
    result.appendItem(*this);
1648
0
    return result;
1649
0
}
1650
1651
QPDFObjectHandle
1652
QPDFObjectHandle::parse(std::string const& object_str, std::string const& object_description)
1653
47.0k
{
1654
47.0k
    return parse(nullptr, object_str, object_description);
1655
47.0k
}
1656
1657
QPDFObjectHandle
1658
QPDFObjectHandle::parse(
1659
    QPDF* context, std::string const& object_str, std::string const& object_description)
1660
47.0k
{
1661
47.0k
    auto input = is::OffsetBuffer("parsed object", object_str);
1662
47.0k
    auto result = Parser::parse(input, object_description, context);
1663
47.0k
    size_t offset = QIntC::to_size(input.tell());
1664
1.08M
    while (offset < object_str.length()) {
1665
1.03M
        if (!isspace(object_str.at(offset))) {
1666
141
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1667
141
            throw QPDFExc(
1668
141
                qpdf_e_damaged_pdf,
1669
141
                "parsed object",
1670
141
                object_description,
1671
141
                input.getLastOffset(),
1672
141
                "trailing data found parsing object from string");
1673
141
        }
1674
1.03M
        ++offset;
1675
1.03M
    }
1676
46.9k
    return result;
1677
47.0k
}
1678
1679
void
1680
QPDFObjectHandle::pipePageContents(Pipeline* p)
1681
0
{
1682
0
    std::string description = "page object " + getObjGen().unparse(' ');
1683
0
    std::string all_description;
1684
0
    getKey("/Contents").pipeContentStreams(p, description, all_description);
1685
0
}
1686
1687
void
1688
QPDFObjectHandle::pipeContentStreams(
1689
    Pipeline* p, std::string const& description, std::string& all_description)
1690
44.8k
{
1691
44.8k
    bool need_newline = false;
1692
44.8k
    std::string buffer;
1693
44.8k
    pl::String buf(buffer);
1694
44.8k
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1695
40.2k
        if (need_newline) {
1696
10.5k
            buf.writeCStr("\n");
1697
10.5k
        }
1698
40.2k
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1699
1.98k
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1700
1.98k
            throw QPDFExc(
1701
1.98k
                qpdf_e_damaged_pdf,
1702
1.98k
                "content stream",
1703
1.98k
                "content stream object " + stream.getObjGen().unparse(' '),
1704
1.98k
                0,
1705
1.98k
                "errors while decoding content stream");
1706
1.98k
        }
1707
38.3k
        need_newline = buffer.empty() || buffer.back() != '\n';
1708
38.3k
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1709
38.3k
        p->writeString(buffer);
1710
38.3k
        buffer.clear();
1711
38.3k
    }
1712
42.8k
    p->finish();
1713
42.8k
}
1714
1715
void
1716
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1717
37.3k
{
1718
37.3k
    std::string description = "page object " + getObjGen().unparse(' ');
1719
37.3k
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1720
37.3k
}
1721
1722
void
1723
QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks)
1724
0
{
1725
0
    std::string description = "object " + getObjGen().unparse(' ');
1726
0
    parseContentStream_internal(description, callbacks);
1727
0
}
1728
1729
void
1730
QPDFObjectHandle::filterPageContents(TokenFilter* filter, Pipeline* next)
1731
0
{
1732
0
    auto description = "token filter for page object " + getObjGen().unparse(' ');
1733
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1734
0
    pipePageContents(&token_pipeline);
1735
0
}
1736
1737
void
1738
QPDFObjectHandle::filterAsContents(TokenFilter* filter, Pipeline* next)
1739
0
{
1740
0
    auto description = "token filter for object " + getObjGen().unparse(' ');
1741
0
    Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
1742
0
    pipeStreamData(&token_pipeline, 0, qpdf_dl_specialized);
1743
0
}
1744
1745
void
1746
QPDFObjectHandle::parseContentStream(QPDFObjectHandle stream_or_array, ParserCallbacks* callbacks)
1747
0
{
1748
0
    stream_or_array.parseContentStream_internal("content stream objects", callbacks);
1749
0
}
1750
1751
void
1752
QPDFObjectHandle::parseContentStream_internal(
1753
    std::string const& description, ParserCallbacks* callbacks)
1754
37.3k
{
1755
37.3k
    std::string stream_data;
1756
37.3k
    pl::String buf(stream_data);
1757
37.3k
    std::string all_description;
1758
37.3k
    pipeContentStreams(&buf, description, all_description);
1759
37.3k
    if (callbacks) {
1760
0
        callbacks->contentSize(stream_data.size());
1761
0
    }
1762
37.3k
    try {
1763
37.3k
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1764
37.3k
    } catch (TerminateParsing&) {
1765
0
        return;
1766
0
    }
1767
34.5k
    if (callbacks) {
1768
0
        callbacks->handleEOF();
1769
0
    }
1770
34.5k
}
1771
1772
void
1773
QPDFObjectHandle::parseContentStream_data(
1774
    std::string_view stream_data,
1775
    std::string const& description,
1776
    ParserCallbacks* callbacks,
1777
    QPDF* context)
1778
34.5k
{
1779
34.5k
    size_t stream_length = stream_data.size();
1780
34.5k
    auto input = is::OffsetBuffer(description, stream_data);
1781
34.5k
    Tokenizer tokenizer;
1782
34.5k
    tokenizer.allowEOF();
1783
34.5k
    auto max_bad_count = Limits::parser_max_errors();
1784
34.5k
    auto sp_description = Parser::make_description(description, "content");
1785
15.1M
    while (QIntC::to_size(input.tell()) < stream_length) {
1786
        // Read a token and seek to the beginning. The offset we get from this process is the
1787
        // beginning of the next non-ignorable (space, comment) token. This way, the offset and
1788
        // don't including ignorable content.
1789
15.0M
        tokenizer.nextToken(input, "content", true);
1790
15.0M
        qpdf_offset_t offset = input.getLastOffset();
1791
15.0M
        input.seek(offset, SEEK_SET);
1792
15.0M
        auto [obj, empty] = Parser::parse_content(input, sp_description, tokenizer, context);
1793
15.0M
        if (empty) {
1794
            // EOF
1795
7.49k
            return;
1796
7.49k
        }
1797
15.0M
        if (!obj) {
1798
8.59k
            if (max_bad_count && --max_bad_count == 0) {
1799
319
                Limits::error();
1800
319
                warn(
1801
319
                    context,
1802
319
                    {qpdf_e_damaged_pdf,
1803
319
                     description,
1804
319
                     "stream data",
1805
319
                     input.tell(),
1806
319
                     "limits error(parser-max-errors): too many errors during content stream "
1807
319
                     "parsing"});
1808
319
                return;
1809
319
            }
1810
8.27k
            obj = newNull();
1811
8.27k
        }
1812
15.0M
        size_t length = QIntC::to_size(input.tell() - offset);
1813
15.0M
        if (callbacks) {
1814
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1815
0
        }
1816
15.0M
        if (obj.isOperator() && obj.getOperatorValue() == "ID") {
1817
            // Discard next character; it is the space after ID that terminated the token.  Read
1818
            // until end of inline image.
1819
3.62k
            char ch;
1820
3.62k
            input.read(&ch, 1);
1821
3.62k
            tokenizer.expectInlineImage(input);
1822
3.62k
            tokenizer.nextToken(input, description);
1823
3.62k
            offset = input.getLastOffset();
1824
3.62k
            length = QIntC::to_size(input.tell() - offset);
1825
3.62k
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1826
162
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1827
162
                warn(
1828
162
                    context,
1829
162
                    {qpdf_e_damaged_pdf,
1830
162
                     description,
1831
162
                     "stream data",
1832
162
                     input.tell(),
1833
162
                     "EOF found while reading inline image"});
1834
3.46k
            } else {
1835
3.46k
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1836
3.46k
                if (callbacks) {
1837
0
                    callbacks->handleObject(
1838
0
                        QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1839
0
                        QIntC::to_size(offset),
1840
0
                        length);
1841
0
                }
1842
3.46k
            }
1843
3.62k
        }
1844
15.0M
    }
1845
34.5k
}
1846
1847
void
1848
QPDFObjectHandle::addContentTokenFilter(std::shared_ptr<TokenFilter> filter)
1849
0
{
1850
0
    coalesceContentStreams();
1851
0
    getKey("/Contents").addTokenFilter(filter);
1852
0
}
1853
1854
void
1855
QPDFObjectHandle::addTokenFilter(std::shared_ptr<TokenFilter> filter)
1856
0
{
1857
0
    return as_stream(error).addTokenFilter(filter);
1858
0
}
1859
1860
QPDFObjectHandle
1861
QPDFObjectHandle::parse(
1862
    std::shared_ptr<InputSource> input,
1863
    std::string const& object_description,
1864
    QPDFTokenizer& tokenizer,
1865
    bool& empty,
1866
    StringDecrypter* decrypter,
1867
    QPDF* context)
1868
0
{
1869
0
    return Parser::parse(*input, object_description, tokenizer, empty, decrypter, context);
1870
0
}
1871
1872
qpdf_offset_t
1873
QPDFObjectHandle::getParsedOffset() const
1874
0
{
1875
0
    return offset();
1876
0
}
1877
1878
QPDFObjectHandle
1879
QPDFObjectHandle::newBool(bool value)
1880
6.29k
{
1881
6.29k
    return {QPDFObject::create<QPDF_Bool>(value)};
1882
6.29k
}
1883
1884
QPDFObjectHandle
1885
QPDFObjectHandle::newNull()
1886
260k
{
1887
260k
    return {QPDFObject::create<QPDF_Null>()};
1888
260k
}
1889
1890
QPDFObjectHandle
1891
QPDFObjectHandle::newReal(std::string const& value)
1892
17.9k
{
1893
17.9k
    return {QPDFObject::create<QPDF_Real>(value)};
1894
17.9k
}
1895
1896
QPDFObjectHandle
1897
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1898
336k
{
1899
336k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
336k
}
1901
1902
QPDFObjectHandle
1903
QPDFObjectHandle::newOperator(std::string const& value)
1904
0
{
1905
0
    return {QPDFObject::create<QPDF_Operator>(value)};
1906
0
}
1907
1908
QPDFObjectHandle
1909
QPDFObjectHandle::newInlineImage(std::string const& value)
1910
0
{
1911
0
    return {QPDFObject::create<QPDF_InlineImage>(value)};
1912
0
}
1913
1914
QPDFObjectHandle
1915
QPDFObjectHandle::newArray()
1916
23.1k
{
1917
23.1k
    return newArray(std::vector<QPDFObjectHandle>());
1918
23.1k
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1922
119k
{
1923
119k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
119k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
84.2k
{
1929
84.2k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
84.2k
}
1931
1932
QPDFObjectHandle
1933
QPDFObjectHandle::newArray(Matrix const& matrix)
1934
0
{
1935
0
    return newArray(
1936
0
        {newReal(matrix.a),
1937
0
         newReal(matrix.b),
1938
0
         newReal(matrix.c),
1939
0
         newReal(matrix.d),
1940
0
         newReal(matrix.e),
1941
0
         newReal(matrix.f)});
1942
0
}
1943
1944
QPDFObjectHandle
1945
QPDFObjectHandle::newArray(QPDFMatrix const& matrix)
1946
0
{
1947
0
    return newArray(
1948
0
        {newReal(matrix.a),
1949
0
         newReal(matrix.b),
1950
0
         newReal(matrix.c),
1951
0
         newReal(matrix.d),
1952
0
         newReal(matrix.e),
1953
0
         newReal(matrix.f)});
1954
0
}
1955
1956
QPDFObjectHandle
1957
QPDFObjectHandle::newFromRectangle(Rectangle const& rect)
1958
9.93k
{
1959
9.93k
    return newArray(rect);
1960
9.93k
}
1961
1962
QPDFObjectHandle
1963
QPDFObjectHandle::newFromMatrix(Matrix const& m)
1964
0
{
1965
0
    return newArray(m);
1966
0
}
1967
1968
QPDFObjectHandle
1969
QPDFObjectHandle::newFromMatrix(QPDFMatrix const& m)
1970
0
{
1971
0
    return newArray(m);
1972
0
}
1973
1974
QPDFObjectHandle
1975
QPDFObjectHandle::newDictionary()
1976
217k
{
1977
217k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
217k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
217k
{
1983
217k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
217k
}
1985
1986
QPDFObjectHandle
1987
QPDFObjectHandle::newStream(QPDF* qpdf)
1988
7.50k
{
1989
7.50k
    if (qpdf == nullptr) {
1990
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1991
0
    }
1992
7.50k
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1993
7.50k
    return qpdf->newStream();
1994
7.50k
}
1995
1996
QPDFObjectHandle
1997
QPDFObjectHandle::newStream(QPDF* qpdf, std::shared_ptr<Buffer> data)
1998
0
{
1999
0
    if (qpdf == nullptr) {
2000
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2001
0
    }
2002
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with data");
2003
0
    return qpdf->newStream(data);
2004
0
}
2005
2006
QPDFObjectHandle
2007
QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
2008
0
{
2009
0
    if (qpdf == nullptr) {
2010
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
2011
0
    }
2012
0
    QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
2013
0
    return qpdf->newStream(data);
2014
0
}
2015
2016
QPDFObjectHandle
2017
QPDFObjectHandle::newReserved(QPDF* qpdf)
2018
0
{
2019
0
    if (qpdf == nullptr) {
2020
0
        throw std::runtime_error("attempt to create reserved object in null qpdf object");
2021
0
    }
2022
0
    return qpdf->newReserved();
2023
0
}
2024
2025
std::string
2026
BaseHandle::description() const
2027
885k
{
2028
885k
    return obj ? obj->getDescription() : ""s;
2029
885k
}
2030
2031
void
2032
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2033
46.4k
{
2034
46.4k
    if (obj) {
2035
46.4k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2036
46.4k
        obj->setDescription(owning_qpdf, descr);
2037
46.4k
    }
2038
46.4k
}
2039
2040
bool
2041
QPDFObjectHandle::hasObjectDescription() const
2042
4.00M
{
2043
4.00M
    return obj && obj->hasDescription();
2044
4.00M
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
82.1k
{
2049
82.1k
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
82.1k
    return {copy()};
2053
82.1k
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::unsafeShallowCopy()
2057
459k
{
2058
459k
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
459k
    return {copy(true)};
2062
459k
}
2063
2064
void
2065
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2066
258k
{
2067
258k
    static uint32_t constexpr max_nesting = 500;
2068
258k
    if (++level > max_nesting + 1) {
2069
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2070
0
    }
2071
258k
    assertInitialized();
2072
2073
258k
    auto cur_og = getObjGen();
2074
258k
    if (!visited.add(cur_og)) {
2075
275
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2076
275
    }
2077
2078
258k
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2079
196k
        obj = copy(true);
2080
196k
    } else if (auto a = as_array(strict)) {
2081
27.2k
        std::vector<QPDFObjectHandle> items;
2082
167k
        for (auto const& item: a) {
2083
167k
            items.emplace_back(item);
2084
167k
            items.back().makeDirect(level, visited, stop_at_streams);
2085
167k
        }
2086
27.2k
        obj = QPDFObject::create<QPDF_Array>(items);
2087
34.4k
    } else if (isDictionary()) {
2088
34.4k
        std::map<std::string, QPDFObjectHandle> items;
2089
115k
        for (auto const& [key, value]: as_dictionary(strict)) {
2090
115k
            if (!value.null()) {
2091
89.7k
                items.insert({key, value});
2092
89.7k
                items[key].makeDirect(level, visited, stop_at_streams);
2093
89.7k
            }
2094
115k
        }
2095
34.4k
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2096
34.4k
    } else if (isStream()) {
2097
16
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2098
16
        if (!stop_at_streams) {
2099
16
            throw std::runtime_error("attempt to make a stream into a direct object");
2100
16
        }
2101
16
    } else if (isReserved()) {
2102
0
        throw std::logic_error(
2103
0
            "QPDFObjectHandle: attempting to make a reserved object handle direct");
2104
0
    } else {
2105
0
        throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type");
2106
0
    }
2107
2108
258k
    visited.erase(cur_og);
2109
258k
}
2110
2111
void
2112
QPDFObjectHandle::makeDirect(bool allow_streams)
2113
1.34k
{
2114
1.34k
    QPDFObjGen::set visited;
2115
1.34k
    makeDirect(0, visited, allow_streams);
2116
1.34k
}
2117
2118
void
2119
QPDFObjectHandle::assertInitialized() const
2120
258k
{
2121
258k
    if (!obj) {
2122
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2123
0
    }
2124
258k
}
2125
2126
std::invalid_argument
2127
BaseHandle::invalid_error(std::string const& method) const
2128
11.7k
{
2129
11.7k
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
11.7k
}
2131
std::runtime_error
2132
BaseHandle::type_error(char const* expected_type) const
2133
303
{
2134
303
    return std::runtime_error(
2135
303
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2136
303
}
2137
2138
QPDFExc
2139
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2140
84.9k
{
2141
84.9k
    return {
2142
84.9k
        qpdf_e_object,
2143
84.9k
        "",
2144
84.9k
        description(),
2145
84.9k
        0,
2146
84.9k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
84.9k
            message};
2148
84.9k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
84.9k
{
2153
84.9k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
84.9k
    warn(type_error(expected_type, message));
2157
84.9k
}
2158
2159
void
2160
QPDFObjectHandle::warnIfPossible(std::string const& warning) const
2161
0
{
2162
0
    warn(warning);
2163
0
}
2164
2165
void
2166
QPDFObjectHandle::objectWarning(std::string const& warning) const
2167
113
{
2168
113
    warn({qpdf_e_object, "", description(), 0, warning});
2169
113
}
2170
2171
void
2172
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2173
11.7k
{
2174
11.7k
    if (!istype) {
2175
303
        throw type_error(type_name);
2176
303
    }
2177
11.7k
}
2178
2179
void
2180
QPDFObjectHandle::assertNull() const
2181
0
{
2182
0
    assertType("null", isNull());
2183
0
}
2184
2185
void
2186
QPDFObjectHandle::assertBool() const
2187
0
{
2188
0
    assertType("boolean", isBool());
2189
0
}
2190
2191
void
2192
QPDFObjectHandle::assertInteger() const
2193
0
{
2194
0
    assertType("integer", isInteger());
2195
0
}
2196
2197
void
2198
QPDFObjectHandle::assertReal() const
2199
0
{
2200
0
    assertType("real", isReal());
2201
0
}
2202
2203
void
2204
QPDFObjectHandle::assertName() const
2205
0
{
2206
0
    assertType("name", isName());
2207
0
}
2208
2209
void
2210
QPDFObjectHandle::assertString() const
2211
0
{
2212
0
    assertType("string", isString());
2213
0
}
2214
2215
void
2216
QPDFObjectHandle::assertOperator() const
2217
0
{
2218
0
    assertType("operator", isOperator());
2219
0
}
2220
2221
void
2222
QPDFObjectHandle::assertInlineImage() const
2223
0
{
2224
0
    assertType("inlineimage", isInlineImage());
2225
0
}
2226
2227
void
2228
QPDFObjectHandle::assertArray() const
2229
0
{
2230
0
    assertType("array", isArray());
2231
0
}
2232
2233
void
2234
QPDFObjectHandle::assertDictionary() const
2235
0
{
2236
0
    assertType("dictionary", isDictionary());
2237
0
}
2238
2239
void
2240
QPDFObjectHandle::assertStream() const
2241
11.4k
{
2242
11.4k
    assertType("stream", isStream());
2243
11.4k
}
2244
2245
void
2246
QPDFObjectHandle::assertReserved() const
2247
0
{
2248
0
    assertType("reserved", isReserved());
2249
0
}
2250
2251
void
2252
QPDFObjectHandle::assertIndirect() const
2253
0
{
2254
0
    if (!isIndirect()) {
2255
0
        throw std::logic_error("operation for indirect object attempted on direct object");
2256
0
    }
2257
0
}
2258
2259
void
2260
QPDFObjectHandle::assertScalar() const
2261
0
{
2262
0
    assertType("scalar", isScalar());
2263
0
}
2264
2265
void
2266
QPDFObjectHandle::assertNumber() const
2267
0
{
2268
0
    assertType("number", isNumber());
2269
0
}
2270
2271
bool
2272
QPDFObjectHandle::isPageObject() const
2273
0
{
2274
    // See comments in QPDFObjectHandle.hh.
2275
0
    if (!qpdf()) {
2276
0
        return false;
2277
0
    }
2278
    // getAllPages repairs /Type when traversing the page tree.
2279
0
    (void)qpdf()->doc().pages().all();
2280
0
    return isDictionaryOfType("/Page");
2281
0
}
2282
2283
bool
2284
QPDFObjectHandle::isPagesObject() const
2285
0
{
2286
0
    if (!qpdf()) {
2287
0
        return false;
2288
0
    }
2289
    // getAllPages repairs /Type when traversing the page tree.
2290
0
    (void)qpdf()->doc().pages().all();
2291
0
    return isDictionaryOfType("/Pages");
2292
0
}
2293
2294
bool
2295
QPDFObjectHandle::isFormXObject() const
2296
143k
{
2297
143k
    return isStreamOfType("", "/Form");
2298
143k
}
2299
2300
bool
2301
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2302
25.5k
{
2303
25.5k
    return (
2304
25.5k
        isStreamOfType("", "/Image") &&
2305
1.67k
        ((!exclude_imagemask) ||
2306
1.67k
         (!(getDict().getKey("/ImageMask").isBool() &&
2307
345
            getDict().getKey("/ImageMask").getBoolValue()))));
2308
25.5k
}
2309
2310
void
2311
QPDFObjectHandle::assertPageObject() const
2312
0
{
2313
0
    if (!isPageObject()) {
2314
0
        throw std::runtime_error("page operation called on non-Page object");
2315
0
    }
2316
0
}
2317
2318
void
2319
BaseHandle::warn(QPDF* qpdf, QPDFExc&& e)
2320
481
{
2321
481
    if (!qpdf) {
2322
0
        throw std::move(e);
2323
0
    }
2324
481
    qpdf->warn(std::move(e));
2325
481
}
2326
2327
void
2328
BaseHandle::warn(QPDFExc&& e) const
2329
936k
{
2330
936k
    if (!qpdf()) {
2331
4.22k
        throw std::move(e);
2332
4.22k
    }
2333
932k
    qpdf()->warn(std::move(e));
2334
932k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
938k
{
2339
938k
    if (qpdf()) {
2340
799k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
799k
    } else {
2342
139k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
139k
    }
2344
938k
}
2345
2346
QPDFObjectHandle::QPDFDictItems::QPDFDictItems(QPDFObjectHandle const& oh) :
2347
0
    oh(oh)
2348
0
{
2349
0
}
2350
2351
QPDFObjectHandle::QPDFDictItems::iterator&
2352
QPDFObjectHandle::QPDFDictItems::iterator::operator++()
2353
0
{
2354
0
    ++m->iter;
2355
0
    updateIValue();
2356
0
    return *this;
2357
0
}
2358
2359
QPDFObjectHandle::QPDFDictItems::iterator&
2360
QPDFObjectHandle::QPDFDictItems::iterator::operator--()
2361
0
{
2362
0
    --m->iter;
2363
0
    updateIValue();
2364
0
    return *this;
2365
0
}
2366
2367
QPDFObjectHandle::QPDFDictItems::iterator::reference
2368
QPDFObjectHandle::QPDFDictItems::iterator::operator*()
2369
0
{
2370
0
    updateIValue();
2371
0
    return ivalue;
2372
0
}
2373
2374
QPDFObjectHandle::QPDFDictItems::iterator::pointer
2375
QPDFObjectHandle::QPDFDictItems::iterator::operator->()
2376
0
{
2377
0
    updateIValue();
2378
0
    return &ivalue;
2379
0
}
2380
2381
bool
2382
QPDFObjectHandle::QPDFDictItems::iterator::operator==(iterator const& other) const
2383
0
{
2384
0
    if (m->is_end && other.m->is_end) {
2385
0
        return true;
2386
0
    }
2387
0
    if (m->is_end || other.m->is_end) {
2388
0
        return false;
2389
0
    }
2390
0
    return (ivalue.first == other.ivalue.first);
2391
0
}
2392
2393
QPDFObjectHandle::QPDFDictItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2394
0
    m(new Members(oh, for_begin))
2395
0
{
2396
0
    updateIValue();
2397
0
}
2398
2399
void
2400
QPDFObjectHandle::QPDFDictItems::iterator::updateIValue()
2401
0
{
2402
0
    m->is_end = (m->iter == m->keys.end());
2403
0
    if (m->is_end) {
2404
0
        ivalue.first = "";
2405
0
        ivalue.second = QPDFObjectHandle();
2406
0
    } else {
2407
0
        ivalue.first = *(m->iter);
2408
0
        ivalue.second = m->oh.getKey(ivalue.first);
2409
0
    }
2410
0
}
2411
2412
QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2413
0
    oh(oh)
2414
0
{
2415
0
    keys = oh.getKeys();
2416
0
    iter = for_begin ? keys.begin() : keys.end();
2417
0
}
2418
2419
QPDFObjectHandle::QPDFDictItems::iterator
2420
QPDFObjectHandle::QPDFDictItems::begin()
2421
0
{
2422
0
    return {oh, true};
2423
0
}
2424
2425
QPDFObjectHandle::QPDFDictItems::iterator
2426
QPDFObjectHandle::QPDFDictItems::end()
2427
0
{
2428
0
    return {oh, false};
2429
0
}
2430
2431
QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) :
2432
9.59k
    oh(oh)
2433
9.59k
{
2434
9.59k
}
2435
2436
QPDFObjectHandle::QPDFArrayItems::iterator&
2437
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2438
352k
{
2439
352k
    if (!m->is_end) {
2440
352k
        ++m->item_number;
2441
352k
        updateIValue();
2442
352k
    }
2443
352k
    return *this;
2444
352k
}
2445
2446
QPDFObjectHandle::QPDFArrayItems::iterator&
2447
QPDFObjectHandle::QPDFArrayItems::iterator::operator--()
2448
0
{
2449
0
    if (m->item_number > 0) {
2450
0
        --m->item_number;
2451
0
        updateIValue();
2452
0
    }
2453
0
    return *this;
2454
0
}
2455
2456
QPDFObjectHandle::QPDFArrayItems::iterator::reference
2457
QPDFObjectHandle::QPDFArrayItems::iterator::operator*()
2458
352k
{
2459
352k
    updateIValue();
2460
352k
    return ivalue;
2461
352k
}
2462
2463
QPDFObjectHandle::QPDFArrayItems::iterator::pointer
2464
QPDFObjectHandle::QPDFArrayItems::iterator::operator->()
2465
0
{
2466
0
    updateIValue();
2467
0
    return &ivalue;
2468
0
}
2469
2470
bool
2471
QPDFObjectHandle::QPDFArrayItems::iterator::operator==(iterator const& other) const
2472
362k
{
2473
362k
    return (m->item_number == other.m->item_number);
2474
362k
}
2475
2476
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2477
19.1k
    m(new Members(oh, for_begin))
2478
19.1k
{
2479
19.1k
    updateIValue();
2480
19.1k
}
2481
2482
void
2483
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2484
724k
{
2485
724k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2486
724k
    if (m->is_end) {
2487
19.1k
        ivalue = QPDFObjectHandle();
2488
705k
    } else {
2489
705k
        ivalue = m->oh.getArrayItem(m->item_number);
2490
705k
    }
2491
724k
}
2492
2493
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2494
19.1k
    oh(oh)
2495
19.1k
{
2496
19.1k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2497
19.1k
}
2498
2499
QPDFObjectHandle::QPDFArrayItems::iterator
2500
QPDFObjectHandle::QPDFArrayItems::begin()
2501
9.59k
{
2502
9.59k
    return {oh, true};
2503
9.59k
}
2504
2505
QPDFObjectHandle::QPDFArrayItems::iterator
2506
QPDFObjectHandle::QPDFArrayItems::end()
2507
9.59k
{
2508
9.59k
    return {oh, false};
2509
9.59k
}
2510
2511
QPDFObjGen
2512
QPDFObjectHandle::getObjGen() const
2513
55.8M
{
2514
55.8M
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
55.8M
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
24.7M
{
2520
24.7M
    return getObjGen().getObj();
2521
24.7M
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
1.45M
{
2532
1.45M
    return getObjectID() != 0;
2533
1.45M
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
2.13M
{
2539
2.13M
    return obj ? obj->getQPDF() : nullptr;
2540
2.13M
}
2541
2542
QPDF&
2543
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2544
7.50k
{
2545
7.50k
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2546
7.50k
        return *result;
2547
7.50k
    }
2548
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2549
7.50k
}
2550
2551
void
2552
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2553
356
{
2554
356
    if (obj) {
2555
356
        obj->setParsedOffset(offset);
2556
356
    }
2557
356
}
2558
2559
QPDFObjectHandle
2560
operator""_qpdf(char const* v, size_t len)
2561
0
{
2562
0
    return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal");
2563
0
}