Coverage Report

Created: 2026-07-30 07:14

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
457k
{
35
457k
    return obj ? obj->getObjGen() : QPDFObjGen();
36
457k
}
37
38
namespace
39
{
40
    class TerminateParsing
41
    {
42
    };
43
} // namespace
44
45
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) :
46
30.3k
    supports_retry(supports_retry)
47
30.3k
{
48
30.3k
}
49
50
QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default)
51
30.3k
{
52
    // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
53
30.3k
}
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.45k
{
86
4.45k
    return supports_retry;
87
4.45k
}
88
89
namespace
90
{
91
    class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
92
    {
93
      public:
94
        CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) :
95
4.45k
            containing_page(containing_page),
96
4.45k
            old_contents(old_contents)
97
4.45k
        {
98
4.45k
        }
99
4.45k
        ~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.45k
{
111
4.45k
    QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
112
4.45k
    std::string description = "page object " + containing_page.getObjGen().unparse(' ');
113
4.45k
    std::string all_description;
114
4.45k
    old_contents.pipeContentStreams(p, description, all_description);
115
4.45k
}
116
117
void
118
QPDFObjectHandle::TokenFilter::handleEOF()
119
13.8k
{
120
13.8k
}
121
122
void
123
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
124
54.6k
{
125
54.6k
    pipeline = p;
126
54.6k
}
127
128
void
129
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
130
219k
{
131
219k
    if (!pipeline) {
132
0
        return;
133
0
    }
134
219k
    if (len) {
135
206k
        pipeline->write(data, len);
136
206k
    }
137
219k
}
138
139
void
140
QPDFObjectHandle::TokenFilter::write(std::string const& str)
141
135k
{
142
135k
    write(str.c_str(), str.length());
143
135k
}
144
145
void
146
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
147
83.9k
{
148
83.9k
    std::string const& value = token.getRawValue();
149
83.9k
    write(value.c_str(), value.length());
150
83.9k
}
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
295k
{
181
295k
    int tail = 0;       // Number of continuation characters expected.
182
295k
    bool tail2 = false; // Potential overlong 3 octet utf-8.
183
295k
    bool tail3 = false; // potential overlong 4 octet
184
295k
    bool needs_escaping = false;
185
15.6M
    for (auto const& it: name) {
186
15.6M
        auto c = static_cast<unsigned char>(it);
187
15.6M
        if (tail) {
188
13.8k
            if ((c & 0xc0) != 0x80) {
189
4.96k
                return {false, false};
190
4.96k
            }
191
8.88k
            if (tail2) {
192
434
                if ((c & 0xe0) == 0x80) {
193
74
                    return {false, false};
194
74
                }
195
360
                tail2 = false;
196
8.45k
            } else if (tail3) {
197
677
                if ((c & 0xf0) == 0x80) {
198
255
                    return {false, false};
199
255
                }
200
422
                tail3 = false;
201
422
            }
202
8.55k
            tail--;
203
15.6M
        } else if (c < 0x80) {
204
15.6M
            if (!needs_escaping) {
205
2.27M
                needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33);
206
2.27M
            }
207
15.6M
        } else if ((c & 0xe0) == 0xc0) {
208
3.19k
            if ((c & 0xfe) == 0xc0) {
209
509
                return {false, false};
210
509
            }
211
2.68k
            tail = 1;
212
23.3k
        } else if ((c & 0xf0) == 0xe0) {
213
3.05k
            tail2 = (c == 0xe0);
214
3.05k
            tail = 2;
215
20.3k
        } else if ((c & 0xf8) == 0xf0) {
216
3.27k
            tail3 = (c == 0xf0);
217
3.27k
            tail = 3;
218
17.0k
        } else {
219
17.0k
            return {false, false};
220
17.0k
        }
221
15.6M
    }
222
272k
    return {tail == 0, !needs_escaping};
223
295k
}
224
225
std::string
226
Name::normalize(std::string const& name)
227
67.7k
{
228
67.7k
    if (name.empty()) {
229
0
        return name;
230
0
    }
231
67.7k
    std::string result;
232
67.7k
    result += name.at(0);
233
44.1M
    for (size_t i = 1; i < name.length(); ++i) {
234
44.0M
        char ch = name.at(i);
235
        // Don't use locale/ctype here; follow PDF spec guidelines.
236
44.0M
        if (ch == '\0') {
237
            // QPDFTokenizer embeds a null character to encode an invalid #.
238
8.14k
            result += "#";
239
44.0M
        } else if (
240
44.0M
            ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' ||
241
43.4M
            ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) {
242
43.4M
            result += util::hex_encode_char(ch);
243
43.4M
        } else {
244
632k
            result += ch;
245
632k
        }
246
44.0M
    }
247
67.7k
    return result;
248
67.7k
}
249
250
std::shared_ptr<QPDFObject>
251
BaseHandle::copy(bool shallow) const
252
882k
{
253
882k
    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
78.3k
    case ::ot_null:
260
78.3k
        return QPDFObject::create<QPDF_Null>();
261
903
    case ::ot_boolean:
262
903
        return QPDFObject::create<QPDF_Bool>(std::get<QPDF_Bool>(obj->value).val);
263
463k
    case ::ot_integer:
264
463k
        return QPDFObject::create<QPDF_Integer>(std::get<QPDF_Integer>(obj->value).val);
265
52.7k
    case ::ot_real:
266
52.7k
        return QPDFObject::create<QPDF_Real>(std::get<QPDF_Real>(obj->value).val);
267
50.0k
    case ::ot_string:
268
50.0k
        return QPDFObject::create<QPDF_String>(std::get<QPDF_String>(obj->value).val);
269
161k
    case ::ot_name:
270
161k
        return QPDFObject::create<QPDF_Name>(std::get<QPDF_Name>(obj->value).name);
271
39.4k
    case ::ot_array:
272
39.4k
        {
273
39.4k
            auto const& a = std::get<QPDF_Array>(obj->value);
274
39.4k
            if (shallow) {
275
0
                return QPDFObject::create<QPDF_Array>(a);
276
39.4k
            } else {
277
39.4k
                QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1);
278
39.4k
                if (a.sp) {
279
457
                    QPDF_Array result;
280
457
                    result.sp = std::make_unique<QPDF_Array::Sparse>();
281
457
                    result.sp->size = a.sp->size;
282
7.20k
                    for (auto const& [idx, oh]: a.sp->elements) {
283
7.20k
                        result.sp->elements[idx] = oh.indirect() ? oh : oh.copy();
284
7.20k
                    }
285
457
                    return QPDFObject::create<QPDF_Array>(std::move(result));
286
39.0k
                } else {
287
39.0k
                    std::vector<QPDFObjectHandle> result;
288
39.0k
                    result.reserve(a.elements.size());
289
729k
                    for (auto const& element: a.elements) {
290
729k
                        result.emplace_back(
291
729k
                            element ? (element.indirect() ? element : element.copy()) : element);
292
729k
                    }
293
39.0k
                    return QPDFObject::create<QPDF_Array>(std::move(result), false);
294
39.0k
                }
295
39.4k
            }
296
39.4k
        }
297
36.3k
    case ::ot_dictionary:
298
36.3k
        {
299
36.3k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
300
36.3k
            if (shallow) {
301
0
                return QPDFObject::create<QPDF_Dictionary>(d.items);
302
36.3k
            } else {
303
36.3k
                std::map<std::string, QPDFObjectHandle> new_items;
304
268k
                for (auto const& [key, val]: d.items) {
305
268k
                    new_items[key] = val.indirect() ? val : val.copy();
306
268k
                }
307
36.3k
                return QPDFObject::create<QPDF_Dictionary>(new_items);
308
36.3k
            }
309
36.3k
        }
310
16
    case ::ot_stream:
311
16
        QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream");
312
16
        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
882k
    }
327
0
    return {}; // unreachable
328
882k
}
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
342k
{
475
342k
    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
63.6k
    case ::ot_null:
483
63.6k
        return "null";
484
424
    case ::ot_boolean:
485
424
        return std::get<QPDF_Bool>(obj->value).val ? "true" : "false";
486
109k
    case ::ot_integer:
487
109k
        return std::to_string(std::get<QPDF_Integer>(obj->value).val);
488
3.12k
    case ::ot_real:
489
3.12k
        return std::get<QPDF_Real>(obj->value).val;
490
122k
    case ::ot_string:
491
122k
        return std::get<QPDF_String>(obj->value).unparse(false);
492
44.2k
    case ::ot_name:
493
44.2k
        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
342k
    }
545
0
    return {}; // unreachable
546
342k
}
547
548
void
549
BaseHandle::write_json(int json_version, JSON::Writer& p) const
550
490k
{
551
490k
    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.7k
    case ::ot_null:
557
19.7k
    case ::ot_operator:
558
19.7k
    case ::ot_inlineimage:
559
19.7k
        p << "null";
560
19.7k
        break;
561
4.19k
    case ::ot_boolean:
562
4.19k
        p << std::get<QPDF_Bool>(obj->value).val;
563
4.19k
        break;
564
164k
    case ::ot_integer:
565
164k
        p << std::to_string(std::get<QPDF_Integer>(obj->value).val);
566
164k
        break;
567
52.1k
    case ::ot_real:
568
52.1k
        {
569
52.1k
            auto const& val = std::get<QPDF_Real>(obj->value).val;
570
52.1k
            if (val.empty()) {
571
                // Can't really happen...
572
0
                p << "0";
573
52.1k
            } else if (val.at(0) == '.') {
574
675
                p << "0" << val;
575
51.4k
            } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') {
576
2.22k
                p << "-0." << val.substr(2);
577
49.2k
            } else {
578
49.2k
                p << val;
579
49.2k
            }
580
52.1k
            if (val.back() == '.') {
581
640
                p << "0";
582
640
            }
583
52.1k
        }
584
52.1k
        break;
585
25.5k
    case ::ot_string:
586
25.5k
        std::get<QPDF_String>(obj->value).writeJSON(json_version, p);
587
25.5k
        break;
588
112k
    case ::ot_name:
589
112k
        {
590
112k
            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
112k
            if (json_version == 1) {
594
0
                p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
595
112k
            } else {
596
112k
                if (auto res = Name::analyzeJSONEncoding(n.name); res.first) {
597
102k
                    if (res.second) {
598
98.6k
                        p << "\"" << n.name << "\"";
599
98.6k
                    } else {
600
3.74k
                        p << "\"" << JSON::Writer::encode_string(n.name) << "\"";
601
3.74k
                    }
602
102k
                } else {
603
10.4k
                    p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\"";
604
10.4k
                }
605
112k
            }
606
112k
        }
607
112k
        break;
608
55.7k
    case ::ot_array:
609
55.7k
        {
610
55.7k
            auto const& a = std::get<QPDF_Array>(obj->value);
611
55.7k
            p.writeStart('[');
612
55.7k
            if (a.sp) {
613
485
                size_t next = 0;
614
8.65k
                for (auto& [key, value]: a.sp->elements) {
615
51.2k
                    for (size_t j = next; j < key; ++j) {
616
42.6k
                        p.writeNext() << "null";
617
42.6k
                    }
618
8.65k
                    p.writeNext();
619
8.65k
                    auto item_og = value.id_gen();
620
8.65k
                    if (item_og.isIndirect()) {
621
544
                        p << "\"" << item_og.unparse(' ') << " R\"";
622
8.10k
                    } else {
623
8.10k
                        value.write_json(json_version, p);
624
8.10k
                    }
625
8.65k
                    next = key + 1;
626
8.65k
                }
627
15.9k
                for (size_t j = next; j < a.sp->size; ++j) {
628
15.4k
                    p.writeNext() << "null";
629
15.4k
                }
630
55.3k
            } else {
631
339k
                for (auto const& item: a.elements) {
632
339k
                    p.writeNext();
633
339k
                    auto item_og = item.id_gen();
634
339k
                    if (item_og.isIndirect()) {
635
9.83k
                        p << "\"" << item_og.unparse(' ') << " R\"";
636
329k
                    } else {
637
329k
                        item.write_json(json_version, p);
638
329k
                    }
639
339k
                }
640
55.3k
            }
641
55.7k
            p.writeEnd(']');
642
55.7k
        }
643
55.7k
        break;
644
56.3k
    case ::ot_dictionary:
645
56.3k
        {
646
56.3k
            auto const& d = std::get<QPDF_Dictionary>(obj->value);
647
56.3k
            p.writeStart('{');
648
220k
            for (auto& iter: d.items) {
649
220k
                if (!iter.second.null()) {
650
182k
                    p.writeNext();
651
182k
                    if (json_version == 1) {
652
0
                        p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first))
653
0
                          << "\": ";
654
182k
                    } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) {
655
169k
                        if (res.second) {
656
165k
                            p << "\"" << iter.first << "\": ";
657
165k
                        } else {
658
4.09k
                            p << "\"" << JSON::Writer::encode_string(iter.first) << "\": ";
659
4.09k
                        }
660
169k
                    } else {
661
12.9k
                        p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first))
662
12.9k
                          << "\": ";
663
12.9k
                    }
664
182k
                    iter.second.writeJSON(json_version, p);
665
182k
                }
666
220k
            }
667
56.3k
            p.writeEnd('}');
668
56.3k
        }
669
56.3k
        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
490k
    }
679
490k
}
680
681
void
682
BaseHandle::disconnect(bool only_direct)
683
4.14M
{
684
    // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here.
685
4.14M
    if (only_direct && indirect()) {
686
351k
        return;
687
351k
    }
688
689
3.79M
    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.0k
                for (auto& item: a.sp->elements) {
695
34.0k
                    item.second.disconnect();
696
34.0k
                }
697
205k
            } else {
698
2.23M
                for (auto& oh: a.elements) {
699
2.23M
                    oh.disconnect();
700
2.23M
                }
701
205k
            }
702
205k
        }
703
205k
        break;
704
305k
    case ::ot_dictionary:
705
1.29M
        for (auto& iter: std::get<QPDF_Dictionary>(obj->value).items) {
706
1.29M
            iter.second.disconnect();
707
1.29M
        }
708
305k
        break;
709
45.9k
    case ::ot_stream:
710
45.9k
        {
711
45.9k
            auto& s = std::get<QPDF_Stream>(obj->value);
712
45.9k
            s.m->stream_provider = nullptr;
713
45.9k
            s.m->stream_dict.disconnect();
714
45.9k
        }
715
45.9k
        break;
716
0
    case ::ot_uninitialized:
717
0
        return;
718
3.23M
    default:
719
3.23M
        break;
720
3.79M
    }
721
3.79M
    obj->qpdf = nullptr;
722
3.79M
    obj->og = QPDFObjGen();
723
3.79M
}
724
725
bool
726
QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const
727
141
{
728
141
    return obj == rhs.obj;
729
141
}
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
16.0k
{
740
16.0k
    static constexpr std::array<char const*, 16> tn{
741
16.0k
        "uninitialized",
742
16.0k
        "reserved",
743
16.0k
        "null",
744
16.0k
        "boolean",
745
16.0k
        "integer",
746
16.0k
        "real",
747
16.0k
        "string",
748
16.0k
        "name",
749
16.0k
        "array",
750
16.0k
        "dictionary",
751
16.0k
        "stream",
752
16.0k
        "operator",
753
16.0k
        "inline-image",
754
16.0k
        "unresolved",
755
16.0k
        "destroyed",
756
16.0k
        "reference"};
757
16.0k
    return tn[type_code()];
758
16.0k
}
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
318k
{
775
318k
    return type_code() == ::ot_boolean;
776
318k
}
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
162k
{
789
162k
    return type_code() == ::ot_null;
790
162k
}
791
792
bool
793
QPDFObjectHandle::isInteger() const
794
997k
{
795
997k
    return type_code() == ::ot_integer;
796
997k
}
797
798
bool
799
QPDFObjectHandle::isReal() const
800
557k
{
801
557k
    return type_code() == ::ot_real;
802
557k
}
803
804
bool
805
QPDFObjectHandle::isNumber() const
806
415k
{
807
415k
    return (isInteger() || isReal());
808
415k
}
809
810
double
811
QPDFObjectHandle::getNumericValue() const
812
175k
{
813
175k
    if (isInteger()) {
814
17.8k
        return static_cast<double>(getIntValue());
815
157k
    } else if (isReal()) {
816
157k
        return atof(getRealValue().c_str());
817
157k
    } else {
818
0
        typeWarning("number", "returning 0");
819
0
        QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
820
0
        return 0;
821
0
    }
822
175k
}
823
824
bool
825
QPDFObjectHandle::getValueAsNumber(double& value) const
826
175k
{
827
175k
    if (!isNumber()) {
828
400
        return false;
829
400
    }
830
175k
    value = getNumericValue();
831
175k
    return true;
832
175k
}
833
834
bool
835
QPDFObjectHandle::isName() const
836
220k
{
837
220k
    return type_code() == ::ot_name;
838
220k
}
839
840
bool
841
QPDFObjectHandle::isString() const
842
142k
{
843
142k
    return type_code() == ::ot_string;
844
142k
}
845
846
bool
847
QPDFObjectHandle::isOperator() const
848
3.83M
{
849
3.83M
    return type_code() == ::ot_operator;
850
3.83M
}
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
74.8k
{
861
74.8k
    return type_code() == ::ot_array;
862
74.8k
}
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
324k
{
873
324k
    return type_code() == ::ot_stream;
874
324k
}
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
298k
{
885
298k
    return isBool() || isInteger() || isName() || isNull() || isReal() || isString();
886
298k
}
887
888
bool
889
QPDFObjectHandle::isNameAndEquals(std::string const& name) const
890
4.55k
{
891
4.55k
    return Name(*this) == name;
892
4.55k
}
893
894
bool
895
QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const
896
1.12M
{
897
1.12M
    return isDictionary() && (type.empty() || Name((*this)["/Type"]) == type) &&
898
158k
        (subtype.empty() || Name((*this)["/Subtype"]) == subtype);
899
1.12M
}
900
901
bool
902
QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) const
903
217k
{
904
217k
    if (auto stream = as_stream()) {
905
37.5k
        return stream && (type.empty() || stream.Type() == type) &&
906
25.4k
            (subtype.empty() || stream.Subtype() == subtype);
907
37.5k
    }
908
179k
    return false;
909
217k
}
910
911
// Bool accessors
912
913
bool
914
QPDFObjectHandle::getBoolValue() const
915
5.48k
{
916
5.48k
    if (auto boolean = as<QPDF_Bool>()) {
917
5.48k
        return boolean->val;
918
5.48k
    } else {
919
0
        typeWarning("boolean", "returning false");
920
0
        QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
921
0
        return false;
922
0
    }
923
5.48k
}
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
38.0k
    BaseHandle(QPDFObject::create<QPDF_Integer>(value))
939
38.0k
{
940
38.0k
}
941
942
QPDFObjectHandle
943
QPDFObjectHandle::newInteger(long long value)
944
4.32k
{
945
4.32k
    return {QPDFObject::create<QPDF_Integer>(value)};
946
4.32k
}
947
948
int64_t
949
Integer::value() const
950
174k
{
951
174k
    auto* i = as<QPDF_Integer>();
952
174k
    if (!i) {
953
1.07k
        throw invalid_error("Integer");
954
1.07k
    }
955
172k
    return i->val;
956
174k
}
957
958
long long
959
QPDFObjectHandle::getIntValue() const
960
52.4k
{
961
52.4k
    if (auto const integer = Integer(*this)) {
962
52.4k
        return integer;
963
52.4k
    } else {
964
0
        typeWarning("integer", "returning 0");
965
0
        return 0;
966
0
    }
967
52.4k
}
968
969
bool
970
QPDFObjectHandle::getValueAsInt(long long& value) const
971
7.17k
{
972
7.17k
    if (auto const integer = Integer(*this)) {
973
7.09k
        value = integer;
974
7.09k
        return true;
975
7.09k
    }
976
75
    return false;
977
7.17k
}
978
979
int
980
QPDFObjectHandle::getIntValueAsInt() const
981
45.6k
{
982
45.6k
    try {
983
45.6k
        return Integer(*this).value<int>();
984
45.6k
    } catch (std::invalid_argument&) {
985
35
        typeWarning("integer", "returning 0");
986
35
        return 0;
987
35
    }
988
45.6k
}
989
990
bool
991
QPDFObjectHandle::getValueAsInt(int& value) const
992
1.38k
{
993
1.38k
    if (!isInteger()) {
994
47
        return false;
995
47
    }
996
1.33k
    value = getIntValueAsInt();
997
1.33k
    return true;
998
1.38k
}
999
1000
unsigned long long
1001
QPDFObjectHandle::getUIntValue() const
1002
26.7k
{
1003
26.7k
    try {
1004
26.7k
        return Integer(*this).value<unsigned long long>();
1005
26.7k
    } catch (std::invalid_argument&) {
1006
661
        typeWarning("integer", "returning 0");
1007
661
        return 0;
1008
661
    }
1009
26.7k
}
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.32k
{
1024
7.32k
    try {
1025
7.32k
        return Integer(*this).value<unsigned int>();
1026
7.32k
    } catch (std::invalid_argument&) {
1027
374
        typeWarning("integer", "returning 0");
1028
374
        return 0;
1029
374
    }
1030
7.32k
}
1031
1032
bool
1033
QPDFObjectHandle::getValueAsUInt(unsigned int& value) const
1034
1.55k
{
1035
1.55k
    if (!isInteger()) {
1036
171
        return false;
1037
171
    }
1038
1.38k
    value = getUIntValueAsUInt();
1039
1.38k
    return true;
1040
1.55k
}
1041
1042
// Real accessors
1043
1044
std::string
1045
QPDFObjectHandle::getRealValue() const
1046
157k
{
1047
157k
    if (auto* real = as<QPDF_Real>()) {
1048
157k
        return real->val;
1049
157k
    }
1050
0
    typeWarning("real", "returning 0.0");
1051
0
    return "0.0";
1052
157k
}
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.7k
{
1069
11.7k
    return {QPDFObject::create<QPDF_Name>(name)};
1070
11.7k
}
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
54.6k
    BaseHandle(QPDFObject::create<QPDF_Name>(std::move(name)))
1079
54.6k
{
1080
54.6k
}
1081
1082
std::string const&
1083
Name::value() const
1084
334k
{
1085
334k
    auto* n = as<QPDF_Name>();
1086
334k
    if (!n) {
1087
0
        throw invalid_error("Name");
1088
0
    }
1089
334k
    return n->name;
1090
334k
}
1091
1092
std::string
1093
QPDFObjectHandle::getName() const
1094
45.9k
{
1095
45.9k
    if (auto* name = as<QPDF_Name>()) {
1096
42.2k
        return name->name;
1097
42.2k
    }
1098
3.68k
    typeWarning("name", "returning dummy name");
1099
3.68k
    return "/QPDFFakeName";
1100
45.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
53.1k
{
1117
53.1k
    return {QPDFObject::create<QPDF_String>(str)};
1118
53.1k
}
1119
1120
QPDFObjectHandle
1121
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
1122
1.08k
{
1123
1.08k
    return {String::utf16(utf8_str).obj_sp()};
1124
1.08k
}
1125
1126
String::String(std::string const& str) :
1127
711
    BaseHandle(QPDFObject::create<QPDF_String>(str))
1128
711
{
1129
711
}
1130
1131
String::String(std::string&& str) :
1132
369
    BaseHandle(QPDFObject::create<QPDF_String>(std::move(str)))
1133
369
{
1134
369
}
1135
1136
String
1137
String::utf16(std::string const& utf8_str)
1138
1.08k
{
1139
1.08k
    std::string result;
1140
1.08k
    if (QUtil::utf8_to_pdf_doc(utf8_str, result, '?')) {
1141
711
        return String(result);
1142
711
    }
1143
369
    return String(QUtil::utf8_to_utf16(utf8_str));
1144
1.08k
}
1145
1146
std::string const&
1147
String::value() const
1148
3.74k
{
1149
3.74k
    auto* s = as<QPDF_String>();
1150
3.74k
    if (!s) {
1151
0
        throw invalid_error("String");
1152
0
    }
1153
3.74k
    return s->val;
1154
3.74k
}
1155
1156
std::string
1157
String::utf8_value() const
1158
257k
{
1159
257k
    auto* s = as<QPDF_String>();
1160
257k
    if (!s) {
1161
0
        throw invalid_error("String");
1162
0
    }
1163
257k
    if (util::is_utf16(s->val)) {
1164
35.6k
        return QUtil::utf16_to_utf8(s->val);
1165
35.6k
    }
1166
222k
    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.31k
        return s->val.substr(3);
1170
2.31k
    }
1171
219k
    return QUtil::pdf_doc_to_utf8(s->val);
1172
222k
}
1173
1174
std::string
1175
QPDFObjectHandle::getStringValue() const
1176
3.74k
{
1177
3.74k
    try {
1178
3.74k
        return String(obj).value();
1179
3.74k
    } catch (std::invalid_argument&) {
1180
0
        typeWarning("string", "returning empty string");
1181
0
        return {};
1182
0
    }
1183
3.74k
}
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
209k
{
1199
209k
    try {
1200
209k
        return String(obj).utf8_value();
1201
209k
    } catch (std::invalid_argument&) {
1202
0
        typeWarning("string", "returning empty string");
1203
0
        return {};
1204
0
    }
1205
209k
}
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.31M
{
1223
1.31M
    if (auto* op = as<QPDF_Operator>()) {
1224
1.31M
        return op->val;
1225
1.31M
    }
1226
0
    typeWarning("operator", "returning fake value");
1227
0
    return "QPDFFAKE";
1228
1.31M
}
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.44k
{
1265
4.44k
    return *this;
1266
4.44k
}
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.29k
{
1281
2.29k
    if (isNameAndEquals(value)) {
1282
123
        return true;
1283
2.17k
    } else if (isArray()) {
1284
2.08k
        for (auto& item: getArrayAsVector()) {
1285
2.08k
            if (item.isNameAndEquals(value)) {
1286
14
                return true;
1287
14
            }
1288
2.08k
        }
1289
520
    }
1290
2.15k
    return false;
1291
2.29k
}
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
34.0k
{
1309
34.0k
    if (!(isDictionary() && other.isDictionary())) {
1310
8.56k
        QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
1311
8.56k
        return;
1312
8.56k
    }
1313
1314
25.4k
    auto make_og_to_name = [](QPDFObjectHandle& dict,
1315
25.4k
                              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.1k
    for (auto const& [rtype, value1]: other.as_dictionary()) {
1326
44.1k
        auto other_val = value1;
1327
44.1k
        if (hasKey(rtype)) {
1328
26.5k
            QPDFObjectHandle this_val = getKey(rtype);
1329
26.5k
            if (this_val.isDictionary() && other_val.isDictionary()) {
1330
20.3k
                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.52k
                    QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
1335
4.52k
                    this_val = replaceKeyAndGetNew(rtype, this_val.shallowCopy());
1336
4.52k
                }
1337
20.3k
                std::map<QPDFObjGen, std::string> og_to_name;
1338
20.3k
                std::set<std::string> rnames;
1339
20.3k
                int min_suffix = 1;
1340
20.3k
                bool initialized_maps = false;
1341
163k
                for (auto const& [key, value2]: other_val.as_dictionary()) {
1342
163k
                    QPDFObjectHandle rval = value2;
1343
163k
                    if (!this_val.hasKey(key)) {
1344
37.0k
                        if (!rval.isIndirect()) {
1345
12.8k
                            QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
1346
12.8k
                            rval = rval.shallowCopy();
1347
12.8k
                        }
1348
37.0k
                        this_val.replaceKey(key, rval);
1349
126k
                    } 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
163k
                }
1371
20.3k
            } else if (this_val.isArray() && other_val.isArray()) {
1372
2.22k
                std::set<std::string> scalars;
1373
100k
                for (auto this_item: this_val.aitems()) {
1374
100k
                    if (this_item.isScalar()) {
1375
99.0k
                        scalars.insert(this_item.unparse());
1376
99.0k
                    }
1377
100k
                }
1378
196k
                for (auto other_item: other_val.aitems()) {
1379
196k
                    if (other_item.isScalar()) {
1380
194k
                        if (!scalars.contains(other_item.unparse())) {
1381
117k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array");
1382
117k
                            this_val.appendItem(other_item);
1383
117k
                        } else {
1384
76.7k
                            QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
1385
76.7k
                        }
1386
194k
                    }
1387
196k
                }
1388
2.22k
            }
1389
26.5k
        } else {
1390
17.5k
            QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
1391
17.5k
            replaceKey(rtype, other_val.shallowCopy());
1392
17.5k
        }
1393
44.1k
    }
1394
25.4k
}
1395
1396
std::set<std::string>
1397
QPDFObjectHandle::getResourceNames() const
1398
16.3k
{
1399
    // Return second-level dictionary keys
1400
16.3k
    std::set<std::string> result;
1401
47.7k
    for (auto const& item: as_dictionary(strict)) {
1402
467k
        for (auto const& [key2, val2]: item.second.as_dictionary(strict)) {
1403
467k
            if (!val2.null()) {
1404
385k
                result.insert(key2);
1405
385k
            }
1406
467k
        }
1407
47.7k
    }
1408
16.3k
    return result;
1409
16.3k
}
1410
1411
std::string
1412
QPDFObjectHandle::getUniqueResourceName(
1413
    std::string const& prefix, int& min_suffix, std::set<std::string>* namesp) const
1414
16.3k
{
1415
16.3k
    std::set<std::string> names = (namesp ? *namesp : getResourceNames());
1416
16.3k
    int max_suffix = min_suffix + QIntC::to_int(names.size());
1417
16.3k
    while (min_suffix <= max_suffix) {
1418
16.3k
        std::string candidate = prefix + std::to_string(min_suffix);
1419
16.3k
        if (!names.contains(candidate)) {
1420
16.3k
            return candidate;
1421
16.3k
        }
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.3k
}
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
31.7k
{
1447
31.7k
    all_description = description;
1448
31.7k
    std::vector<QPDFObjectHandle> result;
1449
31.7k
    if (auto array = as_array(strict)) {
1450
8.72k
        int n_items = static_cast<int>(array.size());
1451
179k
        for (int i = 0; i < n_items; ++i) {
1452
170k
            QPDFObjectHandle item = array[i];
1453
170k
            if (item.isStream()) {
1454
34.9k
                result.emplace_back(item);
1455
135k
            } else {
1456
135k
                item.warn(
1457
135k
                    {qpdf_e_damaged_pdf,
1458
135k
                     "",
1459
135k
                     description + ": item index " + std::to_string(i) + " (from 0)",
1460
135k
                     0,
1461
135k
                     "ignoring non-stream in an array of streams"});
1462
135k
            }
1463
170k
        }
1464
22.9k
    } else if (isStream()) {
1465
8.62k
        result.emplace_back(*this);
1466
14.3k
    } else if (!null()) {
1467
615
        warn(
1468
615
            {qpdf_e_damaged_pdf,
1469
615
             "",
1470
615
             description,
1471
615
             0,
1472
615
             " object is supposed to be a stream or an array of streams but is neither"});
1473
615
    }
1474
1475
31.7k
    bool first = true;
1476
43.4k
    for (auto const& item: result) {
1477
43.4k
        if (first) {
1478
16.5k
            first = false;
1479
26.9k
        } else {
1480
26.9k
            all_description += ",";
1481
26.9k
        }
1482
43.4k
        all_description += " stream " + item.getObjGen().unparse(' ');
1483
43.4k
    }
1484
1485
31.7k
    return result;
1486
31.7k
}
1487
1488
std::vector<QPDFObjectHandle>
1489
QPDFObjectHandle::getPageContents()
1490
7.59k
{
1491
7.59k
    std::string description = "page object " + getObjGen().unparse(' ');
1492
7.59k
    std::string all_description;
1493
7.59k
    return getKey("/Contents").arrayOrStreamToStreamArray(description, all_description);
1494
7.59k
}
1495
1496
void
1497
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
1498
7.59k
{
1499
7.59k
    new_contents.assertStream();
1500
1501
7.59k
    std::vector<QPDFObjectHandle> content_streams;
1502
7.59k
    if (first) {
1503
3.83k
        QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
1504
3.83k
        content_streams.push_back(new_contents);
1505
3.83k
    }
1506
7.59k
    for (auto const& iter: getPageContents()) {
1507
5.00k
        QTC::TC("qpdf", "QPDFObjectHandle append page contents");
1508
5.00k
        content_streams.push_back(iter);
1509
5.00k
    }
1510
7.59k
    if (!first) {
1511
3.76k
        content_streams.push_back(new_contents);
1512
3.76k
    }
1513
1514
7.59k
    replaceKey("/Contents", newArray(content_streams));
1515
7.59k
}
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
19.6k
{
1553
19.6k
    QPDFObjectHandle contents = getKey("/Contents");
1554
19.6k
    if (contents.isStream()) {
1555
4.10k
        QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
1556
4.10k
        return;
1557
15.5k
    } 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
11.1k
        return;
1561
11.1k
    }
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.45k
    QPDF& qpdf = getQPDF("coalesceContentStreams called on object  with no associated PDF file");
1567
1568
4.45k
    QPDFObjectHandle new_contents = newStream(&qpdf);
1569
4.45k
    replaceKey("/Contents", new_contents);
1570
1571
4.45k
    auto provider = std::shared_ptr<StreamDataProvider>(new CoalesceProvider(*this, contents));
1572
4.45k
    new_contents.replaceStreamData(provider, newNull(), newNull());
1573
4.45k
}
1574
1575
std::string
1576
QPDFObjectHandle::unparse() const
1577
346k
{
1578
346k
    if (isIndirect()) {
1579
3.57k
        return getObjGen().unparse(' ') + " R";
1580
342k
    } else {
1581
342k
        return unparseResolved();
1582
342k
    }
1583
346k
}
1584
1585
std::string
1586
QPDFObjectHandle::unparseResolved() const
1587
342k
{
1588
342k
    if (!obj) {
1589
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1590
0
    }
1591
342k
    return BaseHandle::unparse();
1592
342k
}
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.9k
{
1607
16.9k
    if ((!dereference_indirect) && isIndirect()) {
1608
0
        return JSON::makeString(unparse());
1609
16.9k
    } else if (!obj) {
1610
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1611
16.9k
    } else {
1612
16.9k
        Pl_Buffer p{"json"};
1613
16.9k
        JSON::Writer jw{&p, 0};
1614
16.9k
        writeJSON(json_version, jw, dereference_indirect);
1615
16.9k
        p.finish();
1616
16.9k
        return JSON::parse(p.getString());
1617
16.9k
    }
1618
16.9k
}
1619
1620
void
1621
QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) const
1622
199k
{
1623
199k
    if (!dereference_indirect && isIndirect()) {
1624
46.4k
        p << "\"" << getObjGen().unparse(' ') << " R\"";
1625
153k
    } else if (!obj) {
1626
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
1627
153k
    } else {
1628
153k
        write_json(json_version, p);
1629
153k
    }
1630
199k
}
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
24.1k
{
1691
24.1k
    bool need_newline = false;
1692
24.1k
    std::string buffer;
1693
24.1k
    pl::String buf(buffer);
1694
37.9k
    for (auto stream: arrayOrStreamToStreamArray(description, all_description)) {
1695
37.9k
        if (need_newline) {
1696
14.3k
            buf.writeCStr("\n");
1697
14.3k
        }
1698
37.9k
        if (!stream.pipeStreamData(&buf, 0, qpdf_dl_specialized)) {
1699
1.06k
            QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
1700
1.06k
            throw QPDFExc(
1701
1.06k
                qpdf_e_damaged_pdf,
1702
1.06k
                "content stream",
1703
1.06k
                "content stream object " + stream.getObjGen().unparse(' '),
1704
1.06k
                0,
1705
1.06k
                "errors while decoding content stream");
1706
1.06k
        }
1707
36.9k
        need_newline = buffer.empty() || buffer.back() != '\n';
1708
36.9k
        QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1);
1709
36.9k
        p->writeString(buffer);
1710
36.9k
        buffer.clear();
1711
36.9k
    }
1712
23.0k
    p->finish();
1713
23.0k
}
1714
1715
void
1716
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
1717
19.6k
{
1718
19.6k
    std::string description = "page object " + getObjGen().unparse(' ');
1719
19.6k
    getKey("/Contents").parseContentStream_internal(description, callbacks);
1720
19.6k
}
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
19.6k
{
1755
19.6k
    std::string stream_data;
1756
19.6k
    pl::String buf(stream_data);
1757
19.6k
    std::string all_description;
1758
19.6k
    pipeContentStreams(&buf, description, all_description);
1759
19.6k
    if (callbacks) {
1760
0
        callbacks->contentSize(stream_data.size());
1761
0
    }
1762
19.6k
    try {
1763
19.6k
        parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF());
1764
19.6k
    } catch (TerminateParsing&) {
1765
0
        return;
1766
0
    }
1767
18.3k
    if (callbacks) {
1768
0
        callbacks->handleEOF();
1769
0
    }
1770
18.3k
}
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
18.3k
{
1779
18.3k
    size_t stream_length = stream_data.size();
1780
18.3k
    auto input = is::OffsetBuffer(description, stream_data);
1781
18.3k
    Tokenizer tokenizer;
1782
18.3k
    tokenizer.allowEOF();
1783
18.3k
    auto max_bad_count = Limits::parser_max_errors();
1784
18.3k
    auto sp_description = Parser::make_description(description, "content");
1785
3.85M
    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
3.84M
        tokenizer.nextToken(input, "content", true);
1790
3.84M
        qpdf_offset_t offset = input.getLastOffset();
1791
3.84M
        input.seek(offset, SEEK_SET);
1792
3.84M
        auto [obj, empty] = Parser::parse_content(input, sp_description, tokenizer, context);
1793
3.84M
        if (empty) {
1794
            // EOF
1795
5.95k
            return;
1796
5.95k
        }
1797
3.83M
        if (!obj) {
1798
5.83k
            if (max_bad_count && --max_bad_count == 0) {
1799
214
                Limits::error();
1800
214
                warn(
1801
214
                    context,
1802
214
                    {qpdf_e_damaged_pdf,
1803
214
                     description,
1804
214
                     "stream data",
1805
214
                     input.tell(),
1806
214
                     "limits error(parser-max-errors): too many errors during content stream "
1807
214
                     "parsing"});
1808
214
                return;
1809
214
            }
1810
5.62k
            obj = newNull();
1811
5.62k
        }
1812
3.83M
        size_t length = QIntC::to_size(input.tell() - offset);
1813
3.83M
        if (callbacks) {
1814
0
            callbacks->handleObject(obj, QIntC::to_size(offset), length);
1815
0
        }
1816
3.83M
        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
107
                QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
1827
107
                warn(
1828
107
                    context,
1829
107
                    {qpdf_e_damaged_pdf,
1830
107
                     description,
1831
107
                     "stream data",
1832
107
                     input.tell(),
1833
107
                     "EOF found while reading inline image"});
1834
1.93k
            } else {
1835
1.93k
                QTC::TC("qpdf", "QPDFObjectHandle inline image token");
1836
1.93k
                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.93k
            }
1843
2.04k
        }
1844
3.83M
    }
1845
18.3k
}
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
119k
{
1887
119k
    return {QPDFObject::create<QPDF_Null>()};
1888
119k
}
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
65.2k
{
1899
65.2k
    return {QPDFObject::create<QPDF_Real>(value, decimal_places, trim_trailing_zeroes)};
1900
65.2k
}
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
24.5k
{
1923
24.5k
    return {QPDFObject::create<QPDF_Array>(items)};
1924
24.5k
}
1925
1926
QPDFObjectHandle
1927
QPDFObjectHandle::newArray(Rectangle const& rect)
1928
16.3k
{
1929
16.3k
    return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)});
1930
16.3k
}
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.36k
{
1959
7.36k
    return newArray(rect);
1960
7.36k
}
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
21.1k
{
1977
21.1k
    return newDictionary(std::map<std::string, QPDFObjectHandle>());
1978
21.1k
}
1979
1980
QPDFObjectHandle
1981
QPDFObjectHandle::newDictionary(std::map<std::string, QPDFObjectHandle> const& items)
1982
21.1k
{
1983
21.1k
    return {QPDFObject::create<QPDF_Dictionary>(items)};
1984
21.1k
}
1985
1986
QPDFObjectHandle
1987
QPDFObjectHandle::newStream(QPDF* qpdf)
1988
4.45k
{
1989
4.45k
    if (qpdf == nullptr) {
1990
0
        throw std::runtime_error("attempt to create stream in null qpdf object");
1991
0
    }
1992
4.45k
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
1993
4.45k
    return qpdf->newStream();
1994
4.45k
}
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
208k
{
2028
208k
    return obj ? obj->getDescription() : ""s;
2029
208k
}
2030
2031
void
2032
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description)
2033
27.6k
{
2034
27.6k
    if (obj) {
2035
27.6k
        auto descr = std::make_shared<QPDFObject::Description>(object_description);
2036
27.6k
        obj->setDescription(owning_qpdf, descr);
2037
27.6k
    }
2038
27.6k
}
2039
2040
bool
2041
QPDFObjectHandle::hasObjectDescription() const
2042
59.8k
{
2043
59.8k
    return obj && obj->hasDescription();
2044
59.8k
}
2045
2046
QPDFObjectHandle
2047
QPDFObjectHandle::shallowCopy()
2048
41.1k
{
2049
41.1k
    if (!obj) {
2050
0
        throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle");
2051
0
    }
2052
41.1k
    return {copy()};
2053
41.1k
}
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
1.07k
{
2129
1.07k
    return std::invalid_argument(method + " operation attempted on invalid object");
2130
1.07k
}
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
16.0k
{
2141
16.0k
    return {
2142
16.0k
        qpdf_e_object,
2143
16.0k
        "",
2144
16.0k
        description(),
2145
16.0k
        0,
2146
16.0k
        "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " +
2147
16.0k
            message};
2148
16.0k
}
2149
2150
void
2151
QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const
2152
16.0k
{
2153
16.0k
    if (!obj) {
2154
0
        throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle");
2155
0
    }
2156
16.0k
    warn(type_error(expected_type, message));
2157
16.0k
}
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.59k
{
2174
7.59k
    if (!istype) {
2175
0
        throw type_error(type_name);
2176
0
    }
2177
7.59k
}
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.59k
{
2242
7.59k
    assertType("stream", isStream());
2243
7.59k
}
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
76.2k
{
2297
76.2k
    return isStreamOfType("", "/Form");
2298
76.2k
}
2299
2300
bool
2301
QPDFObjectHandle::isImage(bool exclude_imagemask) const
2302
15.6k
{
2303
15.6k
    return (
2304
15.6k
        isStreamOfType("", "/Image") &&
2305
553
        ((!exclude_imagemask) ||
2306
553
         (!(getDict().getKey("/ImageMask").isBool() &&
2307
357
            getDict().getKey("/ImageMask").getBoolValue()))));
2308
15.6k
}
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
321
{
2321
321
    if (!qpdf) {
2322
0
        throw std::move(e);
2323
0
    }
2324
321
    qpdf->warn(std::move(e));
2325
321
}
2326
2327
void
2328
BaseHandle::warn(QPDFExc&& e) const
2329
344k
{
2330
344k
    if (!qpdf()) {
2331
502
        throw std::move(e);
2332
502
    }
2333
343k
    qpdf()->warn(std::move(e));
2334
343k
}
2335
2336
void
2337
BaseHandle::warn(std::string const& warning) const
2338
225k
{
2339
225k
    if (qpdf()) {
2340
192k
        warn({qpdf_e_damaged_pdf, "", description(), 0, warning});
2341
192k
    } else {
2342
33.6k
        *QPDFLogger::defaultLogger()->getError() << warning << "\n";
2343
33.6k
    }
2344
225k
}
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.44k
    oh(oh)
2433
4.44k
{
2434
4.44k
}
2435
2436
QPDFObjectHandle::QPDFArrayItems::iterator&
2437
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
2438
296k
{
2439
296k
    if (!m->is_end) {
2440
296k
        ++m->item_number;
2441
296k
        updateIValue();
2442
296k
    }
2443
296k
    return *this;
2444
296k
}
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
296k
{
2459
296k
    updateIValue();
2460
296k
    return ivalue;
2461
296k
}
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
301k
{
2473
301k
    return (m->item_number == other.m->item_number);
2474
301k
}
2475
2476
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(QPDFObjectHandle& oh, bool for_begin) :
2477
8.88k
    m(new Members(oh, for_begin))
2478
8.88k
{
2479
8.88k
    updateIValue();
2480
8.88k
}
2481
2482
void
2483
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
2484
602k
{
2485
602k
    m->is_end = (m->item_number >= m->oh.getArrayNItems());
2486
602k
    if (m->is_end) {
2487
8.88k
        ivalue = QPDFObjectHandle();
2488
593k
    } else {
2489
593k
        ivalue = m->oh.getArrayItem(m->item_number);
2490
593k
    }
2491
602k
}
2492
2493
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) :
2494
8.88k
    oh(oh)
2495
8.88k
{
2496
8.88k
    item_number = for_begin ? 0 : oh.getArrayNItems();
2497
8.88k
}
2498
2499
QPDFObjectHandle::QPDFArrayItems::iterator
2500
QPDFObjectHandle::QPDFArrayItems::begin()
2501
4.44k
{
2502
4.44k
    return {oh, true};
2503
4.44k
}
2504
2505
QPDFObjectHandle::QPDFArrayItems::iterator
2506
QPDFObjectHandle::QPDFArrayItems::end()
2507
4.44k
{
2508
4.44k
    return {oh, false};
2509
4.44k
}
2510
2511
QPDFObjGen
2512
QPDFObjectHandle::getObjGen() const
2513
908k
{
2514
908k
    return obj ? obj->getObjGen() : QPDFObjGen();
2515
908k
}
2516
2517
int
2518
QPDFObjectHandle::getObjectID() const
2519
664k
{
2520
664k
    return getObjGen().getObj();
2521
664k
}
2522
2523
int
2524
QPDFObjectHandle::getGeneration() const
2525
0
{
2526
0
    return getObjGen().getGen();
2527
0
}
2528
2529
bool
2530
QPDFObjectHandle::isIndirect() const
2531
655k
{
2532
655k
    return getObjectID() != 0;
2533
655k
}
2534
2535
// Indirect object accessors
2536
QPDF*
2537
QPDFObjectHandle::getOwningQPDF() const
2538
473k
{
2539
473k
    return obj ? obj->getQPDF() : nullptr;
2540
473k
}
2541
2542
QPDF&
2543
QPDFObjectHandle::getQPDF(std::string const& error_msg) const
2544
4.45k
{
2545
4.45k
    if (auto result = obj ? obj->getQPDF() : nullptr) {
2546
4.45k
        return *result;
2547
4.45k
    }
2548
0
    throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg);
2549
4.45k
}
2550
2551
void
2552
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
2553
21
{
2554
21
    if (obj) {
2555
21
        obj->setParsedOffset(offset);
2556
21
    }
2557
21
}
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
}