Coverage Report

Created: 2026-07-16 06:55

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
463k
{
35
463k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
463k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
28.6k
    supports_retry(supports_retry)
47
28.6k
{
48
28.6k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
28.6k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
28.6k
}
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
4.18k
{
86
4.18k
    return supports_retry;
87
4.18k
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
4.18k
            containing_page(containing_page),
96
4.18k
            old_contents(old_contents)
97
4.18k
        {
98
4.18k
        }
99
4.18k
        ~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
4.18k
{
111
4.18k
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
4.18k
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
4.18k
    std::string all_description;
114
4.18k
    old_contents.pipeContentStreams(p, description, all_description);
115
4.18k
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
13.7k
{
120
13.7k
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
54.3k
{
125
54.3k
    pipeline = p;
126
54.3k
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
227k
{
131
227k
    if (!pipeline) {
132
0
        return;
133
0
    }
134
227k
    if (len) {
135
214k
        pipeline->write(data, len);
136
214k
    }
137
227k
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
143k
{
142
143k
    write(str.c_str(), str.length());
143
143k
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
84.3k
{
148
84.3k
    std::string const& value = token.getRawValue();
149
84.3k
    write(value.c_str(), value.length());
150
84.3k
}
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
293k
{
181
293k
    int tail = 0;       // Number of continuation characters expected.
182
293k
    bool tail2 = false; // Potential overlong 3 octet utf-8.
183
293k
    bool tail3 = false; // potential overlong 4 octet
184
293k
    bool needs_escaping = false;
185
16.1M
    for (auto const& it: name) {
186
16.1M
        auto c = static_cast<unsigned char>(it);
187
16.1M
        if (tail) {
188
12.7k
            if ((c & 0xc0) != 0x80) {
189
4.81k
                return {false, false};
190
4.81k
            }
191
7.95k
            if (tail2) {
192
343
                if ((c & 0xe0) == 0x80) {
193
101
                    return {false, false};
194
101
                }
195
242
                tail2 = false;
196
7.61k
            } else if (tail3) {
197
691
                if ((c & 0xf0) == 0x80) {
198
353
                    return {false, false};
199
353
                }
200
338
                tail3 = false;
201
338
            }
202
7.50k
            tail--;
203
16.1M
        } else if (c < 0x80) {
204
16.0M
            if (!needs_escaping) {
205
2.43M
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
206
2.43M
            }
207
16.0M
        } else if ((c & 0xe0) == 0xc0) {
208
3.03k
            if ((c & 0xfe) == 0xc0) {
209
503
                return {false, false};
210
503
            }
211
2.53k
            tail = 1;
212
23.0k
        } else if ((c & 0xf0) == 0xe0) {
213
3.09k
            tail2 = (c == 0xe0);
214
3.09k
            tail = 2;
215
19.9k
        } else if ((c & 0xf8) == 0xf0) {
216
3.04k
            tail3 = (c == 0xf0);
217
3.04k
            tail = 3;
218
16.9k
        } else {
219
16.9k
            return {false, false};
220
16.9k
        }
221
16.1M
    }
222
270k
    return {tail == 0, !needs_escaping};
223
293k
}
224
225
std::string
226
Name::normalize(std::string const& name)
227
70.2k
{
228
70.2k
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
70.2k
    std::string result;
232
70.2k
    result += name.at(0);
233
43.5M
    for (size_t i = 1; i < name.length(); ++i) {
234
43.5M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
43.5M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
8.49k
            result += "#";
239
43.4M
        } else if (
240
43.4M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
42.8M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
42.8M
            result += util::hex_encode_char(ch);
243
42.8M
        } else {
244
617k
            result += ch;
245
617k
        }
246
43.5M
    }
247
70.2k
    return result;
248
70.2k
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
895k
{
253
895k
    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
77.5k
    case ::ot_null:
260
77.5k
        return QPDFObject::create<QPDF_Null>();
261
979
    case ::ot_boolean:
262
979
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
468k
    case ::ot_integer:
264
468k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
53.5k
    case ::ot_real:
266
53.5k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
51.9k
    case ::ot_string:
268
51.9k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
165k
    case ::ot_name:
270
165k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
39.9k
    case ::ot_array:
272
39.9k
        {
273
39.9k
            auto const& a = std::get<QPDF_Array>(obj->value);
274
39.9k
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
39.9k
            } else {
277
39.9k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
39.9k
                if (a.sp) {
279
424
                    QPDF_Array result;
280
424
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
424
                    result.sp->size = a.sp->size;
282
7.27k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
7.27k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
7.27k
                    }
285
424
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
39.5k
                } else {
287
39.5k
                    std::vector<QPDFObjectHandle> result;
288
39.5k
                    result.reserve(a.elements.size());
289
741k
                    for (auto const& element: a.elements) {
290
741k
                        result.emplace_back(
291
741k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
741k
                    }
293
39.5k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
39.5k
                }
295
39.9k
            }
296
39.9k
        }
297
37.1k
    case ::ot_dictionary:
298
37.1k
        {
299
37.1k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
37.1k
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
37.1k
            } else {
303
37.1k
                std::map<std::string, QPDFObjectHandle> new_items;
304
276k
                for (auto const& [key, val]: d.items) {
305
276k
                    new_items[key] = val.indirect() ? val : val.copy();
306
276k
                }
307
37.1k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
37.1k
            }
309
37.1k
        }
310
19
    case ::ot_stream:
311
19
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
312
19
        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
895k
    }
327
0
    return {}; // unreachable
328
895k
}
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
364k
{
475
364k
    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
71.0k
    case ::ot_null:
483
71.0k
        return "null";
484
386
    case ::ot_boolean:
485
386
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
113k
    case ::ot_integer:
487
113k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
3.24k
    case ::ot_real:
489
3.24k
        return std::get<QPDF_Real>(obj->value).val;
490
128k
    case ::ot_string:
491
128k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
47.0k
    case ::ot_name:
493
47.0k
        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
364k
    }
545
0
    return {}; // unreachable
546
364k
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
495k
{
551
495k
    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
19.5k
    case ::ot_null:
557
19.5k
    case ::ot_operator:
558
19.5k
    case ::ot_inlineimage:
559
19.5k
        p << "null";
560
19.5k
        break;
561
4.23k
    case ::ot_boolean:
562
4.23k
        p << std::get<QPDF_Bool>(obj->value).val;
563
4.23k
        break;
564
168k
    case ::ot_integer:
565
168k
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
168k
        break;
567
50.6k
    case ::ot_real:
568
50.6k
        {
569
50.6k
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
50.6k
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
50.6k
            } else if (val.at(0) == '.') {
574
790
                p << "0" << val;
575
49.8k
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
2.35k
                p << "-0." << val.substr(2);
577
47.5k
            } else {
578
47.5k
                p << val;
579
47.5k
            }
580
50.6k
            if (val.back() == '.') {
581
632
                p << "0";
582
632
            }
583
50.6k
        }
584
50.6k
        break;
585
26.8k
    case ::ot_string:
586
26.8k
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
587
26.8k
        break;
588
115k
    case ::ot_name:
589
115k
        {
590
115k
            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
115k
            if (json_version == 1) {
594
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
595
115k
            } else {
596
115k
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
597
104k
                    if (res.second) {
598
100k
                        p << "\"" << n.name << "\"";
599
100k
                    } else {
600
3.96k
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
601
3.96k
                    }
602
104k
                } else {
603
10.6k
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
10.6k
                }
605
115k
            }
606
115k
        }
607
115k
        break;
608
56.2k
    case ::ot_array:
609
56.2k
        {
610
56.2k
            auto const& a = std::get<QPDF_Array>(obj->value);
611
56.2k
            p.writeStart('[');
612
56.2k
            if (a.sp) {
613
445
                size_t next = 0;
614
8.37k
                for (auto& [key, value]: a.sp->elements) {
615
49.6k
                    for (size_t j = next; j < key; ++j) {
616
41.2k
                        p.writeNext() << "null";
617
41.2k
                    }
618
8.37k
                    p.writeNext();
619
8.37k
                    auto item_og = value.id_gen();
620
8.37k
                    if (item_og.isIndirect()) {
621
557
                        p << "\"" << item_og.unparse(' ') << " R\"";
622
7.81k
                    } else {
623
7.81k
                        value.write_json(json_version, p);
624
7.81k
                    }
625
8.37k
                    next = key + 1;
626
8.37k
                }
627
14.8k
                for (size_t j = next; j < a.sp->size; ++j) {
628
14.3k
                    p.writeNext() << "null";
629
14.3k
                }
630
55.8k
            } else {
631
348k
                for (auto const& item: a.elements) {
632
348k
                    p.writeNext();
633
348k
                    auto item_og = item.id_gen();
634
348k
                    if (item_og.isIndirect()) {
635
9.93k
                        p << "\"" << item_og.unparse(' ') << " R\"";
636
339k
                    } else {
637
339k
                        item.write_json(json_version, p);
638
339k
                    }
639
348k
                }
640
55.8k
            }
641
56.2k
            p.writeEnd(']');
642
56.2k
        }
643
56.2k
        break;
644
54.4k
    case ::ot_dictionary:
645
54.4k
        {
646
54.4k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
647
54.4k
            p.writeStart('{');
648
216k
            for (auto& iter: d.items) {
649
216k
                if (!iter.second.null()) {
650
178k
                    p.writeNext();
651
178k
                    if (json_version == 1) {
652
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
653
0
                          << "\": ";
654
178k
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
655
165k
                        if (res.second) {
656
161k
                            p << "\"" << iter.first << "\": ";
657
161k
                        } else {
658
4.19k
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
659
4.19k
                        }
660
165k
                    } else {
661
12.5k
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
12.5k
                          << "\": ";
663
12.5k
                    }
664
178k
                    iter.second.writeJSON(json_version, p);
665
178k
                }
666
216k
            }
667
54.4k
            p.writeEnd('}');
668
54.4k
        }
669
54.4k
        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
495k
    }
679
495k
}
680
681
void
682
BaseHandle::disconnect(bool only_direct)
683
4.17M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
4.17M
    if (only_direct && indirect()) {
686
342k
        return;
687
342k
    }
688
689
3.83M
    switch (raw_type_code()) {
690
205k
    case ::ot_array:
691
205k
        {
692
205k
            auto& a = std::get<QPDF_Array>(obj->value);
693
205k
            if (a.sp) {
694
34.6k
                for (auto& item: a.sp->elements) {
695
34.6k
                    item.second.disconnect();
696
34.6k
                }
697
204k
            } else {
698
2.25M
                for (auto& oh: a.elements) {
699
2.25M
                    oh.disconnect();
700
2.25M
                }
701
204k
            }
702
205k
        }
703
205k
        break;
704
303k
    case ::ot_dictionary:
705
1.30M
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
1.30M
            iter.second.disconnect();
707
1.30M
        }
708
303k
        break;
709
45.0k
    case ::ot_stream:
710
45.0k
        {
711
45.0k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
45.0k
            s.m->stream_provider = nullptr;
713
45.0k
            s.m->stream_dict.disconnect();
714
45.0k
        }
715
45.0k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
3.28M
    default:
719
3.28M
        break;
720
3.83M
    }
721
3.83M
    obj->qpdf = nullptr;
722
3.83M
    obj->og = QPDFObjGen();
723
3.83M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
125
{
728
125
    return obj == rhs.obj;
729
125
}
730
731
qpdf_object_type_e
732
QPDFObjectHandle::getTypeCode() const
733
0
{
734
0
    return type_code();
735
0
}
736
737
char const*
738
BaseHandle::type_name() const
739
15.1k
{
740
15.1k
    static constexpr std::array<char const*, 16> tn{
741
15.1k
        "uninitialized",
742
15.1k
        "reserved",
743
15.1k
        "null",
744
15.1k
        "boolean",
745
15.1k
        "integer",
746
15.1k
        "real",
747
15.1k
        "string",
748
15.1k
        "name",
749
15.1k
        "array",
750
15.1k
        "dictionary",
751
15.1k
        "stream",
752
15.1k
        "operator",
753
15.1k
        "inline-image",
754
15.1k
        "unresolved",
755
15.1k
        "destroyed",
756
15.1k
        "reference"};
757
15.1k
    return tn[type_code()];
758
15.1k
}
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
334k
{
775
334k
    return type_code() == ::ot_boolean;
776
334k
}
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
171k
{
789
171k
    return type_code() == ::ot_null;
790
171k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
1.00M
{
795
1.00M
    return type_code() == ::ot_integer;
796
1.00M
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
554k
{
801
554k
    return type_code() == ::ot_real;
802
554k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
412k
{
807
412k
    return (isInteger() || isReal());
808
412k
}
809
810
double
811
QPDFObjectHandle::getNumericValue() const
812
173k
{
813
173k
    if (isInteger()) {
814
18.3k
        return static_cast<double>(getIntValue());
815
155k
    } else if (isReal()) {
816
155k
        return atof(getRealValue().c_str());
817
155k
    } else {
818
0
        typeWarning("number", "returning 0");
819
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
820
0
        return 0;
821
0
    }
822
173k
}
823
824
bool
825
QPDFObjectHandle::getValueAsNumber(double& value) const
826
173k
{
827
173k
    if (!isNumber()) {
828
424
        return false;
829
424
    }
830
173k
    value = getNumericValue();
831
173k
    return true;
832
173k
}
833
834
bool
835
QPDFObjectHandle::isName() const
836
229k
{
837
229k
    return type_code() == ::ot_name;
838
229k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
145k
{
843
145k
    return type_code() == ::ot_string;
844
145k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
2.87M
{
849
2.87M
    return type_code() == ::ot_operator;
850
2.87M
}
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
71.5k
{
861
71.5k
    return type_code() == ::ot_array;
862
71.5k
}
863
864
bool
865
QPDFObjectHandle::isDictionary() const
866
2.19M
{
867
2.19M
    return type_code() == ::ot_dictionary;
868
2.19M
}
869
870
bool
871
QPDFObjectHandle::isStream() const
872
293k
{
873
293k
    return type_code() == ::ot_stream;
874
293k
}
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
315k
{
885
315k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
315k
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
4.99k
{
891
4.99k
    return Name(*this) == name;
892
4.99k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
1.15M
{
897
1.15M
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
155k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
1.15M
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
212k
{
904
212k
    if (auto stream = as_stream()) {
905
36.9k
        return stream && (type.empty() || stream.Type() == type) &&
906
24.7k
            (subtype.empty() || stream.Subtype() == subtype);
907
36.9k
    }
908
175k
    return false;
909
212k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
5.28k
{
916
5.28k
    if (auto boolean = as<QPDF_Bool>()) {
917
5.28k
        return boolean->val;
918
5.28k
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
5.28k
}
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
37.1k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
37.1k
{
940
37.1k
}
941
942
QPDFObjectHandle
943
QPDFObjectHandle::newInteger(long long value)
944
4.04k
{
945
4.04k
    return {QPDFObject::create<QPDF_Integer>(value)};
946
4.04k
}
947
948
int64_t
949
Integer::value() const
950
169k
{
951
169k
    auto* i = as<QPDF_Integer>();
952
169k
    if (!i) {
953
980
        throw invalid_error("Integer");
954
980
    }
955
168k
    return i->val;
956
169k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
51.0k
{
961
51.0k
    if (auto const integer = Integer(*this)) {
962
51.0k
        return integer;
963
51.0k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
51.0k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
6.81k
{
972
6.81k
    if (auto const integer = Integer(*this)) {
973
6.73k
        value = integer;
974
6.73k
        return true;
975
6.73k
    }
976
75
    return false;
977
6.81k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
43.5k
{
982
43.5k
    try {
983
43.5k
        return Integer(*this).value<int>();
984
43.5k
    } catch (std::invalid_argument&) {
985
25
        typeWarning("integer", "returning 0");
986
25
        return 0;
987
25
    }
988
43.5k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
1.37k
{
993
1.37k
    if (!isInteger()) {
994
50
        return false;
995
50
    }
996
1.32k
    value = getIntValueAsInt();
997
1.32k
    return true;
998
1.37k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
27.4k
{
1003
27.4k
    try {
1004
27.4k
        return Integer(*this).value<unsigned long long>();
1005
27.4k
    } catch (std::invalid_argument&) {
1006
605
        typeWarning("integer", "returning 0");
1007
605
        return 0;
1008
605
    }
1009
27.4k
}
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
7.00k
{
1024
7.00k
    try {
1025
7.00k
        return Integer(*this).value<unsigned int>();
1026
7.00k
    } catch (std::invalid_argument&) {
1027
350
        typeWarning("integer", "returning 0");
1028
350
        return 0;
1029
350
    }
1030
7.00k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
1.52k
{
1035
1.52k
    if (!isInteger()) {
1036
155
        return false;
1037
155
    }
1038
1.37k
    value = getUIntValueAsUInt();
1039
1.37k
    return true;
1040
1.52k
}
1041
1042
// Real accessors
1043
1044
std::string
1045
QPDFObjectHandle::getRealValue() const
1046
155k
{
1047
155k
    if (auto* real = as<QPDF_Real>()) {
1048
155k
        return real->val;
1049
155k
    }
1050
0
    typeWarning("real", "returning 0.0");
1051
0
    return "0.0";
1052
155k
}
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
11.6k
{
1069
11.6k
    return {QPDFObject::create<QPDF_Name>(name)};
1070
11.6k
}
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
52.4k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
52.4k
{
1080
52.4k
}
1081
1082
std::string const&
1083
Name::value() const
1084
325k
{
1085
325k
    auto* n = as<QPDF_Name>();
1086
325k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
325k
    return n->name;
1090
325k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
42.9k
{
1095
42.9k
    if (auto* name = as<QPDF_Name>()) {
1096
39.7k
        return name->name;
1097
39.7k
    }
1098
3.19k
    typeWarning("name", "returning dummy name");
1099
3.19k
    return "/QPDFFakeName";
1100
42.9k
}
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
57.2k
{
1117
57.2k
    return {QPDFObject::create<QPDF_String>(str)};
1118
57.2k
}
1119
1120
QPDFObjectHandle
1121
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1122
1.00k
{
1123
1.00k
    return {String::utf16(utf8_str).obj_sp()};
1124
1.00k
}
1125
1126
String::String(std::string const& str) :
1127
678
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1128
678
{
1129
678
}
1130
1131
String::String(std::string&& str) :
1132
325
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1133
325
{
1134
325
}
1135
1136
String
1137
String::utf16(std::string const& utf8_str)
1138
1.00k
{
1139
1.00k
    std::string result;
1140
1.00k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1141
678
        return String(result);
1142
678
    }
1143
325
    return String(QUtil::utf8_to_utf16(utf8_str));
1144
1.00k
}
1145
1146
std::string const&
1147
String::value() const
1148
3.75k
{
1149
3.75k
    auto* s = as<QPDF_String>();
1150
3.75k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
3.75k
    return s->val;
1154
3.75k
}
1155
1156
std::string
1157
String::utf8_value() const
1158
245k
{
1159
245k
    auto* s = as<QPDF_String>();
1160
245k
    if (!s) {
1161
0
        throw invalid_error("String");
1162
0
    }
1163
245k
    if (util::is_utf16(s->val)) {
1164
34.3k
        return QUtil::utf16_to_utf8(s->val);
1165
34.3k
    }
1166
210k
    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
2.10k
        return s->val.substr(3);
1170
2.10k
    }
1171
208k
    return QUtil::pdf_doc_to_utf8(s->val);
1172
210k
}
1173
1174
std::string
1175
QPDFObjectHandle::getStringValue() const
1176
3.75k
{
1177
3.75k
    try {
1178
3.75k
        return String(obj).value();
1179
3.75k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
3.75k
}
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
197k
{
1199
197k
    try {
1200
197k
        return String(obj).utf8_value();
1201
197k
    } catch (std::invalid_argument&) {
1202
0
        typeWarning("string", "returning empty string");
1203
0
        return {};
1204
0
    }
1205
197k
}
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
1.22M
{
1223
1.22M
    if (auto* op = as<QPDF_Operator>()) {
1224
1.22M
        return op->val;
1225
1.22M
    }
1226
0
    typeWarning("operator", "returning fake value");
1227
0
    return "QPDFFAKE";
1228
1.22M
}
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
4.28k
{
1265
4.28k
    return *this;
1266
4.28k
}
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
2.36k
{
1281
2.36k
    if (isNameAndEquals(value)) {
1282
117
        return true;
1283
2.24k
    } else if (isArray()) {
1284
2.45k
        for (auto& item: getArrayAsVector()) {
1285
2.45k
            if (item.isNameAndEquals(value)) {
1286
13
                return true;
1287
13
            }
1288
2.45k
        }
1289
463
    }
1290
2.23k
    return false;
1291
2.36k
}
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
33.5k
{
1309
33.5k
    if (!(isDictionary() && other.isDictionary())) {
1310
8.31k
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1311
8.31k
        return;
1312
8.31k
    }
1313
1314
25.2k
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1315
25.2k
                              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
44.7k
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1326
44.7k
        auto other_val = value1;
1327
44.7k
        if (hasKey(rtype)) {
1328
26.8k
            QPDFObjectHandle this_val = getKey(rtype);
1329
26.8k
            if (this_val.isDictionary() && other_val.isDictionary()) {
1330
20.6k
                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
4.72k
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1335
4.72k
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1336
4.72k
                }
1337
20.6k
                std::map<QPDFObjGen, std::string> og_to_name;
1338
20.6k
                std::set<std::string> rnames;
1339
20.6k
                int min_suffix = 1;
1340
20.6k
                bool initialized_maps = false;
1341
167k
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1342
167k
                    QPDFObjectHandle rval = value2;
1343
167k
                    if (!this_val.hasKey(key)) {
1344
37.6k
                        if (!rval.isIndirect()) {
1345
12.2k
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1346
12.2k
                            rval = rval.shallowCopy();
1347
12.2k
                        }
1348
37.6k
                        this_val.replaceKey(key, rval);
1349
129k
                    } 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
167k
                }
1371
20.6k
            } else if (this_val.isArray() && other_val.isArray()) {
1372
2.14k
                std::set<std::string> scalars;
1373
107k
                for (auto this_item: this_val.aitems()) {
1374
107k
                    if (this_item.isScalar()) {
1375
106k
                        scalars.insert(this_item.unparse());
1376
106k
                    }
1377
107k
                }
1378
206k
                for (auto other_item: other_val.aitems()) {
1379
206k
                    if (other_item.isScalar()) {
1380
204k
                        if (!scalars.contains(other_item.unparse())) {
1381
123k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1382
123k
                            this_val.appendItem(other_item);
1383
123k
                        } else {
1384
81.3k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1385
81.3k
                        }
1386
204k
                    }
1387
206k
                }
1388
2.14k
            }
1389
26.8k
        } else {
1390
17.9k
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1391
17.9k
            replaceKey(rtype, other_val.shallowCopy());
1392
17.9k
        }
1393
44.7k
    }
1394
25.2k
}
1395
1396
std::set<std::string>
1397
QPDFObjectHandle::getResourceNames() const
1398
16.0k
{
1399
    // Return second-level dictionary keys
1400
16.0k
    std::set<std::string> result;
1401
49.0k
    for (auto const& item: as_dictionary(strict)) {
1402
479k
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1403
479k
            if (!val2.null()) {
1404
395k
                result.insert(key2);
1405
395k
            }
1406
479k
        }
1407
49.0k
    }
1408
16.0k
    return result;
1409
16.0k
}
1410
1411
std::string
1412
QPDFObjectHandle::getUniqueResourceName(
1413
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1414
16.0k
{
1415
16.0k
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1416
16.0k
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1417
16.0k
    while (min_suffix <= max_suffix) {
1418
16.0k
        std::string candidate = prefix + std::to_string(min_suffix);
1419
16.0k
        if (!names.contains(candidate)) {
1420
16.0k
            return candidate;
1421
16.0k
        }
1422
        // Increment after return; min_suffix should be the value
1423
        // used, not the next value.
1424
10
        ++min_suffix;
1425
10
    }
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
16.0k
}
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
30.2k
{
1447
30.2k
    all_description = description;
1448
30.2k
    std::vector<QPDFObjectHandle> result;
1449
30.2k
    if (auto array = as_array(strict)) {
1450
8.10k
        int n_items = static_cast<int>(array.size());
1451
151k
        for (int i = 0; i < n_items; ++i) {
1452
143k
            QPDFObjectHandle item = array[i];
1453
143k
            if (item.isStream()) {
1454
28.7k
                result.emplace_back(item);
1455
114k
            } else {
1456
114k
                item.warn(
1457
114k
                    {qpdf_e_damaged_pdf,
1458
114k
                     "",
1459
114k
                     description + ": item index " + std::to_string(i) + " (from 0)",
1460
114k
                     0,
1461
114k
                     "ignoring non-stream in an array of streams"});
1462
114k
            }
1463
143k
        }
1464
22.1k
    } else if (isStream()) {
1465
8.35k
        result.emplace_back(*this);
1466
13.7k
    } else if (!null()) {
1467
595
        warn(
1468
595
            {qpdf_e_damaged_pdf,
1469
595
             "",
1470
595
             description,
1471
595
             0,
1472
595
             " object is supposed to be a stream or an array of streams but is neither"});
1473
595
    }
1474
1475
30.2k
    bool first = true;
1476
37.0k
    for (auto const& item: result) {
1477
37.0k
        if (first) {
1478
15.7k
            first = false;
1479
21.2k
        } else {
1480
21.2k
            all_description += ",";
1481
21.2k
        }
1482
37.0k
        all_description += " stream " + item.getObjGen().unparse(' ');
1483
37.0k
    }
1484
1485
30.2k
    return result;
1486
30.2k
}
1487
1488
std::vector<QPDFObjectHandle>
1489
QPDFObjectHandle::getPageContents()
1490
7.16k
{
1491
7.16k
    std::string description = "page object " + getObjGen().unparse(' ');
1492
7.16k
    std::string all_description;
1493
7.16k
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1494
7.16k
}
1495
1496
void
1497
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1498
7.16k
{
1499
7.16k
    new_contents.assertStream();
1500
1501
7.16k
    std::vector<QPDFObjectHandle> content_streams;
1502
7.16k
    if (first) {
1503
3.62k
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1504
3.62k
        content_streams.push_back(new_contents);
1505
3.62k
    }
1506
7.16k
    for (auto const& iter: getPageContents()) {
1507
4.58k
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1508
4.58k
        content_streams.push_back(iter);
1509
4.58k
    }
1510
7.16k
    if (!first) {
1511
3.54k
        content_streams.push_back(new_contents);
1512
3.54k
    }
1513
1514
7.16k
    replaceKey("/Contents", newArray(content_streams));
1515
7.16k
}
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
18.9k
{
1553
18.9k
    QPDFObjectHandle contents = getKey("/Contents");
1554
18.9k
    if (contents.isStream()) {
1555
4.10k
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1556
4.10k
        return;
1557
14.8k
    } 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
10.6k
        return;
1561
10.6k
    }
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
4.18k
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1567
1568
4.18k
    QPDFObjectHandle new_contents = newStream(&qpdf);
1569
4.18k
    replaceKey("/Contents", new_contents);
1570
1571
4.18k
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1572
4.18k
    new_contents.replaceStreamData(provider, newNull(), newNull());
1573
4.18k
}
1574
1575
std::string
1576
QPDFObjectHandle::unparse() const
1577
367k
{
1578
367k
    if (isIndirect()) {
1579
3.44k
        return getObjGen().unparse(' ') + " R";
1580
364k
    } else {
1581
364k
        return unparseResolved();
1582
364k
    }
1583
367k
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
364k
{
1588
364k
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
364k
    return BaseHandle::unparse();
1592
364k
}
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
16.3k
{
1607
16.3k
    if ((!dereference_indirect) && isIndirect()) {
1608
0
        return JSON::makeString(unparse());
1609
16.3k
    } else if (!obj) {
1610
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1611
16.3k
    } else {
1612
16.3k
        Pl_Buffer p{"json"};
1613
16.3k
        JSON::Writer jw{&p, 0};
1614
16.3k
        writeJSON(json_version, jw, dereference_indirect);
1615
16.3k
        p.finish();
1616
16.3k
        return JSON::parse(p.getString());
1617
16.3k
    }
1618
16.3k
}
1619
1620
void
1621
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1622
194k
{
1623
194k
    if (!dereference_indirect && isIndirect()) {
1624
45.6k
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1625
148k
    } else if (!obj) {
1626
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1627
148k
    } else {
1628
148k
        write_json(json_version, p);
1629
148k
    }
1630
194k
}
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
0
{
1654
0
    return parse(nullptr, object_str, object_description);
1655
0
}
1656
1657
QPDFObjectHandle
1658
QPDFObjectHandle::parse(
1659
    QPDF* context, std::string const& object_str, std::string const& object_description)
1660
0
{
1661
0
    auto input = is::OffsetBuffer("parsed object", object_str);
1662
0
    auto result = Parser::parse(input, object_description, context);
1663
0
    size_t offset = QIntC::to_size(input.tell());
1664
0
    while (offset < object_str.length()) {
1665
0
        if (!isspace(object_str.at(offset))) {
1666
0
            QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
1667
0
            throw QPDFExc(
1668
0
                qpdf_e_damaged_pdf,
1669
0
                "parsed object",
1670
0
                object_description,
1671
0
                input.getLastOffset(),
1672
0
                "trailing data found parsing object from string");
1673
0
        }
1674
0
        ++offset;
1675
0
    }
1676
0
    return result;
1677
0
}
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
23.0k
{
1691
23.0k
    bool need_newline = false;
1692
23.0k
    std::string buffer;
1693
23.0k
    pl::String buf(buffer);
1694
32.1k
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1695
32.1k
        if (need_newline) {
1696
11.3k
            buf.writeCStr("\n");
1697
11.3k
        }
1698
32.1k
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1699
1.05k
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1700
1.05k
            throw QPDFExc(
1701
1.05k
                qpdf_e_damaged_pdf,
1702
1.05k
                "content stream",
1703
1.05k
                "content stream object " + stream.getObjGen().unparse(' '),
1704
1.05k
                0,
1705
1.05k
                "errors while decoding content stream");
1706
1.05k
        }
1707
31.0k
        need_newline = buffer.empty() || buffer.back() != '\n';
1708
31.0k
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1709
31.0k
        p->writeString(buffer);
1710
31.0k
        buffer.clear();
1711
31.0k
    }
1712
22.0k
    p->finish();
1713
22.0k
}
1714
1715
void
1716
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1717
18.9k
{
1718
18.9k
    std::string description = "page object " + getObjGen().unparse(' ');
1719
18.9k
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1720
18.9k
}
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
18.9k
{
1755
18.9k
    std::string stream_data;
1756
18.9k
    pl::String buf(stream_data);
1757
18.9k
    std::string all_description;
1758
18.9k
    pipeContentStreams(&buf, description, all_description);
1759
18.9k
    if (callbacks) {
1760
0
        callbacks->contentSize(stream_data.size());
1761
0
    }
1762
18.9k
    try {
1763
18.9k
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1764
18.9k
    } catch (TerminateParsing&) {
1765
0
        return;
1766
0
    }
1767
17.6k
    if (callbacks) {
1768
0
        callbacks->handleEOF();
1769
0
    }
1770
17.6k
}
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
17.6k
{
1779
17.6k
    size_t stream_length = stream_data.size();
1780
17.6k
    auto input = is::OffsetBuffer(description, stream_data);
1781
17.6k
    Tokenizer tokenizer;
1782
17.6k
    tokenizer.allowEOF();
1783
17.6k
    auto max_bad_count = Limits::parser_max_errors();
1784
17.6k
    auto sp_description = Parser::make_description(description, "content");
1785
2.88M
    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
2.87M
        tokenizer.nextToken(input, "content", true);
1790
2.87M
        qpdf_offset_t offset = input.getLastOffset();
1791
2.87M
        input.seek(offset, SEEK_SET);
1792
2.87M
        auto [obj, empty] = Parser::parse_content(input, sp_description, tokenizer, context);
1793
2.87M
        if (empty) {
1794
            // EOF
1795
5.72k
            return;
1796
5.72k
        }
1797
2.87M
        if (!obj) {
1798
5.61k
            if (max_bad_count && --max_bad_count == 0) {
1799
199
                Limits::error();
1800
199
                warn(
1801
199
                    context,
1802
199
                    {qpdf_e_damaged_pdf,
1803
199
                     description,
1804
199
                     "stream data",
1805
199
                     input.tell(),
1806
199
                     "limits error(parser-max-errors): too many errors during content stream "
1807
199
                     "parsing"});
1808
199
                return;
1809
199
            }
1810
5.41k
            obj = newNull();
1811
5.41k
        }
1812
2.87M
        size_t length = QIntC::to_size(input.tell() - offset);
1813
2.87M
        if (callbacks) {
1814
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1815
0
        }
1816
2.87M
        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
2.04k
            char ch;
1820
2.04k
            input.read(&ch, 1);
1821
2.04k
            tokenizer.expectInlineImage(input);
1822
2.04k
            tokenizer.nextToken(input, description);
1823
2.04k
            offset = input.getLastOffset();
1824
2.04k
            length = QIntC::to_size(input.tell() - offset);
1825
2.04k
            if (tokenizer.getType() == QPDFTokenizer::tt_bad) {
1826
103
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1827
103
                warn(
1828
103
                    context,
1829
103
                    {qpdf_e_damaged_pdf,
1830
103
                     description,
1831
103
                     "stream data",
1832
103
                     input.tell(),
1833
103
                     "EOF found while reading inline image"});
1834
1.94k
            } else {
1835
1.94k
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1836
1.94k
                if (callbacks) {
1837
0
                    callbacks->handleObject(
1838
0
                        QPDFObjectHandle::newInlineImage(tokenizer.getValue()),
1839
0
                        QIntC::to_size(offset),
1840
0
                        length);
1841
0
                }
1842
1.94k
            }
1843
2.04k
        }
1844
2.87M
    }
1845
17.6k
}
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
0
{
1881
0
    return {QPDFObject::create<QPDF_Bool>(value)};
1882
0
}
1883
1884
QPDFObjectHandle
1885
QPDFObjectHandle::newNull()
1886
125k
{
1887
125k
    return {QPDFObject::create<QPDF_Null>()};
1888
125k
}
1889
1890
QPDFObjectHandle
1891
QPDFObjectHandle::newReal(std::string const& value)
1892
0
{
1893
0
    return {QPDFObject::create<QPDF_Real>(value)};
1894
0
}
1895
1896
QPDFObjectHandle
1897
QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes)
1898
63.0k
{
1899
63.0k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
63.0k
}
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
0
{
1917
0
    return newArray(std::vector<QPDFObjectHandle>());
1918
0
}
1919
1920
QPDFObjectHandle
1921
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
1922
23.5k
{
1923
23.5k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
23.5k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
15.7k
{
1929
15.7k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
15.7k
}
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
7.22k
{
1959
7.22k
    return newArray(rect);
1960
7.22k
}
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
19.7k
{
1977
19.7k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
19.7k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
19.7k
{
1983
19.7k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
19.7k
}
1985
1986
QPDFObjectHandle
1987
QPDFObjectHandle::newStream(QPDF* qpdf)
1988
4.18k
{
1989
4.18k
    if (qpdf == nullptr) {
1990
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1991
0
    }
1992
4.18k
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1993
4.18k
    return qpdf->newStream();
1994
4.18k
}
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
204k
{
2028
204k
    return obj ? obj->getDescription() : ""s;
2029
204k
}
2030
2031
void
2032
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2033
26.5k
{
2034
26.5k
    if (obj) {
2035
26.5k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2036
26.5k
        obj->setDescription(owning_qpdf, descr);
2037
26.5k
    }
2038
26.5k
}
2039
2040
bool
2041
QPDFObjectHandle::hasObjectDescription() const
2042
58.4k
{
2043
58.4k
    return obj && obj->hasDescription();
2044
58.4k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
41.2k
{
2049
41.2k
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
41.2k
    return {copy()};
2053
41.2k
}
2054
2055
QPDFObjectHandle
2056
QPDFObjectHandle::unsafeShallowCopy()
2057
0
{
2058
0
    if (!obj) {
2059
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2060
0
    }
2061
0
    return {copy(true)};
2062
0
}
2063
2064
void
2065
QPDFObjectHandle::makeDirect(uint32_t level, QPDFObjGen::set& visited, bool stop_at_streams)
2066
0
{
2067
0
    static uint32_t constexpr max_nesting = 500;
2068
0
    if (++level > max_nesting + 1) {
2069
0
        throw std::runtime_error("QPDFObjectHandle::makeDirect exceeded maximum nesting level");
2070
0
    }
2071
0
    assertInitialized();
2072
2073
0
    auto cur_og = getObjGen();
2074
0
    if (!visited.add(cur_og)) {
2075
0
        throw std::runtime_error("loop detected while converting object from indirect to direct");
2076
0
    }
2077
2078
0
    if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) {
2079
0
        obj = copy(true);
2080
0
    } else if (auto a = as_array(strict)) {
2081
0
        std::vector<QPDFObjectHandle> items;
2082
0
        for (auto const& item: a) {
2083
0
            items.emplace_back(item);
2084
0
            items.back().makeDirect(level, visited, stop_at_streams);
2085
0
        }
2086
0
        obj = QPDFObject::create<QPDF_Array>(items);
2087
0
    } else if (isDictionary()) {
2088
0
        std::map<std::string, QPDFObjectHandle> items;
2089
0
        for (auto const& [key, value]: as_dictionary(strict)) {
2090
0
            if (!value.null()) {
2091
0
                items.insert({key, value});
2092
0
                items[key].makeDirect(level, visited, stop_at_streams);
2093
0
            }
2094
0
        }
2095
0
        obj = QPDFObject::create<QPDF_Dictionary>(items);
2096
0
    } else if (isStream()) {
2097
0
        QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1);
2098
0
        if (!stop_at_streams) {
2099
0
            throw std::runtime_error("attempt to make a stream into a direct object");
2100
0
        }
2101
0
    } 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
0
    visited.erase(cur_og);
2109
0
}
2110
2111
void
2112
QPDFObjectHandle::makeDirect(bool allow_streams)
2113
0
{
2114
0
    QPDFObjGen::set visited;
2115
0
    makeDirect(0, visited, allow_streams);
2116
0
}
2117
2118
void
2119
QPDFObjectHandle::assertInitialized() const
2120
0
{
2121
0
    if (!obj) {
2122
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2123
0
    }
2124
0
}
2125
2126
std::invalid_argument
2127
BaseHandle::invalid_error(std::string const& method) const
2128
980
{
2129
980
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
980
}
2131
std::runtime_error
2132
BaseHandle::type_error(char const* expected_type) const
2133
0
{
2134
0
    return std::runtime_error(
2135
0
        "operation for "s + expected_type + " attempted on object of type " + type_name());
2136
0
}
2137
2138
QPDFExc
2139
BaseHandle::type_error(char const* expected_type, std::string const& message) const
2140
15.1k
{
2141
15.1k
    return {
2142
15.1k
        qpdf_e_object,
2143
15.1k
        "",
2144
15.1k
        description(),
2145
15.1k
        0,
2146
15.1k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
15.1k
            message};
2148
15.1k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
15.1k
{
2153
15.1k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
15.1k
    warn(type_error(expected_type, message));
2157
15.1k
}
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
0
{
2168
0
    warn({qpdf_e_object, "", description(), 0, warning});
2169
0
}
2170
2171
void
2172
QPDFObjectHandle::assertType(char const* type_name, bool istype) const
2173
7.16k
{
2174
7.16k
    if (!istype) {
2175
0
        throw type_error(type_name);
2176
0
    }
2177
7.16k
}
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
7.16k
{
2242
7.16k
    assertType("stream", isStream());
2243
7.16k
}
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
73.2k
{
2297
73.2k
    return isStreamOfType("", "/Form");
2298
73.2k
}
2299
2300
bool
2301
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2302
15.5k
{
2303
15.5k
    return (
2304
15.5k
        isStreamOfType("", "/Image") &&
2305
591
        ((!exclude_imagemask) ||
2306
591
         (!(getDict().getKey("/ImageMask").isBool() &&
2307
404
            getDict().getKey("/ImageMask").getBoolValue()))));
2308
15.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
302
{
2321
302
    if (!qpdf) {
2322
0
        throw std::move(e);
2323
0
    }
2324
302
    qpdf->warn(std::move(e));
2325
302
}
2326
2327
void
2328
BaseHandle::warn(QPDFExc&& e) const
2329
320k
{
2330
320k
    if (!qpdf()) {
2331
490
        throw std::move(e);
2332
490
    }
2333
319k
    qpdf()->warn(std::move(e));
2334
319k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
221k
{
2339
221k
    if (qpdf()) {
2340
189k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
189k
    } else {
2342
32.2k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
32.2k
    }
2344
221k
}
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
4.28k
    oh(oh)
2433
4.28k
{
2434
4.28k
}
2435
2436
QPDFObjectHandle::QPDFArrayItems::iterator&
2437
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2438
314k
{
2439
314k
    if (!m->is_end) {
2440
314k
        ++m->item_number;
2441
314k
        updateIValue();
2442
314k
    }
2443
314k
    return *this;
2444
314k
}
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
314k
{
2459
314k
    updateIValue();
2460
314k
    return ivalue;
2461
314k
}
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
318k
{
2473
318k
    return (m->item_number == other.m->item_number);
2474
318k
}
2475
2476
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2477
8.56k
    m(new Members(oh, for_begin))
2478
8.56k
{
2479
8.56k
    updateIValue();
2480
8.56k
}
2481
2482
void
2483
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2484
637k
{
2485
637k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2486
637k
    if (m->is_end) {
2487
8.56k
        ivalue = QPDFObjectHandle();
2488
628k
    } else {
2489
628k
        ivalue = m->oh.getArrayItem(m->item_number);
2490
628k
    }
2491
637k
}
2492
2493
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2494
8.56k
    oh(oh)
2495
8.56k
{
2496
8.56k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2497
8.56k
}
2498
2499
QPDFObjectHandle::QPDFArrayItems::iterator
2500
QPDFObjectHandle::QPDFArrayItems::begin()
2501
4.28k
{
2502
4.28k
    return {oh, true};
2503
4.28k
}
2504
2505
QPDFObjectHandle::QPDFArrayItems::iterator
2506
QPDFObjectHandle::QPDFArrayItems::end()
2507
4.28k
{
2508
4.28k
    return {oh, false};
2509
4.28k
}
2510
2511
QPDFObjGen
2512
QPDFObjectHandle::getObjGen() const
2513
912k
{
2514
912k
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
912k
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
679k
{
2520
679k
    return getObjGen().getObj();
2521
679k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
671k
{
2532
671k
    return getObjectID() != 0;
2533
671k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
464k
{
2539
464k
    return obj ? obj->getQPDF() : nullptr;
2540
464k
}
2541
2542
QPDF&
2543
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2544
4.18k
{
2545
4.18k
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2546
4.18k
        return *result;
2547
4.18k
    }
2548
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2549
4.18k
}
2550
2551
void
2552
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2553
23
{
2554
23
    if (obj) {
2555
23
        obj->setParsedOffset(offset);
2556
23
    }
2557
23
}
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
}