Coverage Report

Created: 2025-08-26 07:12

/src/qpdf/libqpdf/QPDF_linearization.cc
Line
Count
Source (jump to first uncovered line)
1
// See doc/linearization.
2
3
#include <qpdf/QPDF_private.hh>
4
5
#include <qpdf/BitStream.hh>
6
#include <qpdf/BitWriter.hh>
7
#include <qpdf/InputSource_private.hh>
8
#include <qpdf/Pipeline_private.hh>
9
#include <qpdf/Pl_Buffer.hh>
10
#include <qpdf/Pl_Flate.hh>
11
#include <qpdf/Pl_String.hh>
12
#include <qpdf/QPDFExc.hh>
13
#include <qpdf/QPDFObjectHandle_private.hh>
14
#include <qpdf/QPDFWriter_private.hh>
15
#include <qpdf/QTC.hh>
16
#include <qpdf/QUtil.hh>
17
#include <qpdf/Util.hh>
18
19
#include <algorithm>
20
#include <cmath>
21
#include <cstring>
22
#include <utility>
23
24
using namespace qpdf;
25
using namespace std::literals;
26
27
template <class T, class int_type>
28
static void
29
load_vector_int(
30
    BitStream& bit_stream, int nitems, std::vector<T>& vec, int bits_wanted, int_type T::* field)
31
0
{
32
0
    bool append = vec.empty();
33
    // nitems times, read bits_wanted from the given bit stream, storing results in the ith vector
34
    // entry.
35
36
0
    for (size_t i = 0; i < QIntC::to_size(nitems); ++i) {
37
0
        if (append) {
38
0
            vec.push_back(T());
39
0
        }
40
0
        vec.at(i).*field = bit_stream.getBitsInt(QIntC::to_size(bits_wanted));
41
0
    }
42
0
    if (QIntC::to_int(vec.size()) != nitems) {
43
0
        throw std::logic_error("vector has wrong size in load_vector_int");
44
0
    }
45
    // The PDF spec says that each hint table starts at a byte boundary.  Each "row" actually must
46
    // start on a byte boundary.
47
0
    bit_stream.skipToNextByte();
48
0
}
Unexecuted instantiation: QPDF_linearization.cc:void load_vector_int<QPDF::HPageOffsetEntry, int>(BitStream&, int, std::__1::vector<QPDF::HPageOffsetEntry, std::__1::allocator<QPDF::HPageOffsetEntry> >&, int, int QPDF::HPageOffsetEntry::*)
Unexecuted instantiation: QPDF_linearization.cc:void load_vector_int<QPDF::HPageOffsetEntry, long long>(BitStream&, int, std::__1::vector<QPDF::HPageOffsetEntry, std::__1::allocator<QPDF::HPageOffsetEntry> >&, int, long long QPDF::HPageOffsetEntry::*)
Unexecuted instantiation: QPDF_linearization.cc:void load_vector_int<QPDF::HSharedObjectEntry, int>(BitStream&, int, std::__1::vector<QPDF::HSharedObjectEntry, std::__1::allocator<QPDF::HSharedObjectEntry> >&, int, int QPDF::HSharedObjectEntry::*)
49
50
template <class T>
51
static void
52
load_vector_vector(
53
    BitStream& bit_stream,
54
    int nitems1,
55
    std::vector<T>& vec1,
56
    int T::* nitems2,
57
    int bits_wanted,
58
    std::vector<int> T::* vec2)
59
0
{
60
    // nitems1 times, read nitems2 (from the ith element of vec1) items into the vec2 vector field
61
    // of the ith item of vec1.
62
0
    for (size_t i1 = 0; i1 < QIntC::to_size(nitems1); ++i1) {
63
0
        for (int i2 = 0; i2 < vec1.at(i1).*nitems2; ++i2) {
64
0
            (vec1.at(i1).*vec2).push_back(bit_stream.getBitsInt(QIntC::to_size(bits_wanted)));
65
0
        }
66
0
    }
67
0
    bit_stream.skipToNextByte();
68
0
}
69
70
void
71
QPDF::linearizationWarning(std::string_view msg)
72
0
{
73
0
    m->linearization_warnings = true;
74
0
    warn(qpdf_e_linearization, "", 0, std::string(msg));
75
0
}
76
77
bool
78
QPDF::checkLinearization()
79
0
{
80
0
    try {
81
0
        readLinearizationData();
82
0
        checkLinearizationInternal();
83
0
        return !m->linearization_warnings;
84
0
    } catch (std::runtime_error& e) {
85
0
        linearizationWarning(
86
0
            "error encountered while checking linearization data: " + std::string(e.what()));
87
0
        return false;
88
0
    }
89
0
}
90
91
bool
92
QPDF::isLinearized()
93
0
{
94
    // If the first object in the file is a dictionary with a suitable /Linearized key and has an /L
95
    // key that accurately indicates the file size, initialize m->lindict and return true.
96
97
    // A linearized PDF spec's first object will be contained within the first 1024 bytes of the
98
    // file and will be a dictionary with a valid /Linearized key.  This routine looks for that and
99
    // does no additional validation.
100
101
    // The PDF spec says the linearization dictionary must be completely contained within the first
102
    // 1024 bytes of the file. Add a byte for a null terminator.
103
0
    auto buffer = m->file->read(1024, 0);
104
0
    size_t pos = 0;
105
0
    while (true) {
106
        // Find a digit or end of buffer
107
0
        pos = buffer.find_first_of("0123456789"sv, pos);
108
0
        if (pos == std::string::npos) {
109
0
            return false;
110
0
        }
111
        // Seek to the digit. Then skip over digits for a potential
112
        // next iteration.
113
0
        m->file->seek(toO(pos), SEEK_SET);
114
115
0
        auto t1 = readToken(*m->file, 20);
116
0
        if (!(t1.isInteger() && readToken(*m->file, 6).isInteger() &&
117
0
              readToken(*m->file, 4).isWord("obj"))) {
118
0
            pos = buffer.find_first_not_of("0123456789"sv, pos);
119
0
            if (pos == std::string::npos) {
120
0
                return false;
121
0
            }
122
0
            continue;
123
0
        }
124
125
0
        auto candidate = getObject(toI(QUtil::string_to_ll(t1.getValue().data())), 0);
126
0
        if (!candidate.isDictionary()) {
127
0
            return false;
128
0
        }
129
130
0
        auto linkey = candidate.getKey("/Linearized");
131
0
        if (!(linkey.isNumber() && toI(floor(linkey.getNumericValue())) == 1)) {
132
0
            return false;
133
0
        }
134
135
0
        auto L = candidate.getKey("/L");
136
0
        if (!L.isInteger()) {
137
0
            return false;
138
0
        }
139
0
        qpdf_offset_t Li = L.getIntValue();
140
0
        m->file->seek(0, SEEK_END);
141
0
        if (Li != m->file->tell()) {
142
0
            QTC::TC("qpdf", "QPDF /L mismatch");
143
0
            return false;
144
0
        }
145
0
        m->linp.file_size = Li;
146
0
        m->lindict = candidate;
147
0
        return true;
148
0
    }
149
0
}
150
151
void
152
QPDF::readLinearizationData()
153
0
{
154
    // This function throws an exception (which is trapped by checkLinearization()) for any errors
155
    // that prevent loading.
156
157
0
    if (!isLinearized()) {
158
0
        throw std::logic_error("called readLinearizationData for file that is not linearized");
159
0
    }
160
161
    // /L is read and stored in linp by isLinearized()
162
0
    QPDFObjectHandle H = m->lindict.getKey("/H");
163
0
    QPDFObjectHandle O = m->lindict.getKey("/O");
164
0
    QPDFObjectHandle E = m->lindict.getKey("/E");
165
0
    QPDFObjectHandle N = m->lindict.getKey("/N");
166
0
    QPDFObjectHandle T = m->lindict.getKey("/T");
167
0
    QPDFObjectHandle P = m->lindict.getKey("/P");
168
169
0
    if (!(H.isArray() && O.isInteger() && E.isInteger() && N.isInteger() && T.isInteger() &&
170
0
          (P.isInteger() || P.isNull()))) {
171
0
        throw damagedPDF(
172
0
            "linearization dictionary",
173
0
            "some keys in linearization dictionary are of the wrong type");
174
0
    }
175
176
    // Hint table array: offset length [ offset length ]
177
0
    size_t n_H_items = H.size();
178
0
    if (!(n_H_items == 2 || n_H_items == 4)) {
179
0
        throw damagedPDF("linearization dictionary", "H has the wrong number of items");
180
0
    }
181
182
0
    std::vector<int> H_items;
183
0
    for (auto const& oh: H.as_array()) {
184
0
        if (oh.isInteger()) {
185
0
            H_items.push_back(oh.getIntValueAsInt());
186
0
        } else {
187
0
            throw damagedPDF("linearization dictionary", "some H items are of the wrong type");
188
0
        }
189
0
    }
190
191
    // H: hint table offset/length for primary and overflow hint tables
192
0
    int H0_offset = H_items.at(0);
193
0
    int H0_length = H_items.at(1);
194
0
    int H1_offset = 0;
195
0
    int H1_length = 0;
196
0
    if (H_items.size() == 4) {
197
        // Acrobat doesn't read or write these (as PDF 1.4), so we don't have a way to generate a
198
        // test case.
199
        // QTC::TC("qpdf", "QPDF overflow hint table");
200
0
        H1_offset = H_items.at(2);
201
0
        H1_length = H_items.at(3);
202
0
    }
203
204
    // P: first page number
205
0
    int first_page = 0;
206
0
    if (P.isInteger()) {
207
0
        QTC::TC("qpdf", "QPDF P present in lindict");
208
0
        first_page = P.getIntValueAsInt();
209
0
    } else {
210
0
        QTC::TC("qpdf", "QPDF P absent in lindict");
211
0
    }
212
213
    // Store linearization parameter data
214
215
    // Various places in the code use linp.npages, which is initialized from N, to pre-allocate
216
    // memory, so make sure it's accurate and bail right now if it's not.
217
0
    if (N.getIntValue() != static_cast<long long>(getAllPages().size())) {
218
0
        throw damagedPDF("linearization hint table", "/N does not match number of pages");
219
0
    }
220
221
    // file_size initialized by isLinearized()
222
0
    m->linp.first_page_object = O.getIntValueAsInt();
223
0
    m->linp.first_page_end = E.getIntValue();
224
0
    m->linp.npages = N.getUIntValueAsUInt();
225
0
    m->linp.xref_zero_offset = T.getIntValue();
226
0
    m->linp.first_page = first_page;
227
0
    m->linp.H_offset = H0_offset;
228
0
    m->linp.H_length = H0_length;
229
230
    // Read hint streams
231
232
0
    Pl_Buffer pb("hint buffer");
233
0
    QPDFObjectHandle H0 = readHintStream(pb, H0_offset, toS(H0_length));
234
0
    if (H1_offset) {
235
0
        (void)readHintStream(pb, H1_offset, toS(H1_length));
236
0
    }
237
238
    // PDF 1.4 hint tables that we ignore:
239
240
    //  /T    thumbnail
241
    //  /A    thread information
242
    //  /E    named destination
243
    //  /V    interactive form
244
    //  /I    information dictionary
245
    //  /C    logical structure
246
    //  /L    page label
247
248
    // Individual hint table offsets
249
0
    QPDFObjectHandle HS = H0.getKey("/S"); // shared object
250
0
    QPDFObjectHandle HO = H0.getKey("/O"); // outline
251
252
0
    auto hbp = pb.getBufferSharedPointer();
253
0
    Buffer* hb = hbp.get();
254
0
    unsigned char const* h_buf = hb->getBuffer();
255
0
    size_t h_size = hb->getSize();
256
257
0
    readHPageOffset(BitStream(h_buf, h_size));
258
259
0
    int HSi = HS.getIntValueAsInt();
260
0
    if ((HSi < 0) || (toS(HSi) >= h_size)) {
261
0
        throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds");
262
0
    }
263
0
    readHSharedObject(BitStream(h_buf + HSi, h_size - toS(HSi)));
264
265
0
    if (HO.isInteger()) {
266
0
        int HOi = HO.getIntValueAsInt();
267
0
        if ((HOi < 0) || (toS(HOi) >= h_size)) {
268
0
            throw damagedPDF("linearization hint table", "/O (outline) offset is out of bounds");
269
0
        }
270
0
        readHGeneric(BitStream(h_buf + HOi, h_size - toS(HOi)), m->outline_hints);
271
0
    }
272
0
}
273
274
QPDFObjectHandle
275
QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length)
276
0
{
277
0
    auto H = readObjectAtOffset(offset, "linearization hint stream", false);
278
0
    ObjCache& oc = m->obj_cache[H];
279
0
    qpdf_offset_t min_end_offset = oc.end_before_space;
280
0
    qpdf_offset_t max_end_offset = oc.end_after_space;
281
0
    if (!H.isStream()) {
282
0
        throw damagedPDF("linearization dictionary", "hint table is not a stream");
283
0
    }
284
285
0
    QPDFObjectHandle Hdict = H.getDict();
286
287
    // Some versions of Acrobat make /Length indirect and place it immediately after the stream,
288
    // increasing length to cover it, even though the specification says all objects in the
289
    // linearization parameter dictionary must be direct.  We have to get the file position of the
290
    // end of length in this case.
291
0
    QPDFObjectHandle length_obj = Hdict.getKey("/Length");
292
0
    if (length_obj.isIndirect()) {
293
0
        QTC::TC("qpdf", "QPDF hint table length indirect");
294
        // Force resolution
295
0
        (void)length_obj.getIntValue();
296
0
        ObjCache& oc2 = m->obj_cache[length_obj.getObjGen()];
297
0
        min_end_offset = oc2.end_before_space;
298
0
        max_end_offset = oc2.end_after_space;
299
0
    } else {
300
0
        QTC::TC("qpdf", "QPDF hint table length direct");
301
0
    }
302
0
    qpdf_offset_t computed_end = offset + toO(length);
303
0
    if ((computed_end < min_end_offset) || (computed_end > max_end_offset)) {
304
0
        linearizationWarning(
305
0
            "expected = " + std::to_string(computed_end) +
306
0
            "; actual = " + std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset));
307
0
        throw damagedPDF("linearization dictionary", "hint table length mismatch");
308
0
    }
309
0
    H.pipeStreamData(&pl, 0, qpdf_dl_specialized);
310
0
    return Hdict;
311
0
}
312
313
void
314
QPDF::readHPageOffset(BitStream h)
315
0
{
316
    // All comments referring to the PDF spec refer to the spec for version 1.4.
317
318
0
    HPageOffset& t = m->page_offset_hints;
319
320
0
    t.min_nobjects = h.getBitsInt(32);               // 1
321
0
    t.first_page_offset = h.getBitsInt(32);          // 2
322
0
    t.nbits_delta_nobjects = h.getBitsInt(16);       // 3
323
0
    t.min_page_length = h.getBitsInt(32);            // 4
324
0
    t.nbits_delta_page_length = h.getBitsInt(16);    // 5
325
0
    t.min_content_offset = h.getBitsInt(32);         // 6
326
0
    t.nbits_delta_content_offset = h.getBitsInt(16); // 7
327
0
    t.min_content_length = h.getBitsInt(32);         // 8
328
0
    t.nbits_delta_content_length = h.getBitsInt(16); // 9
329
0
    t.nbits_nshared_objects = h.getBitsInt(16);      // 10
330
0
    t.nbits_shared_identifier = h.getBitsInt(16);    // 11
331
0
    t.nbits_shared_numerator = h.getBitsInt(16);     // 12
332
0
    t.shared_denominator = h.getBitsInt(16);         // 13
333
334
0
    std::vector<HPageOffsetEntry>& entries = t.entries;
335
0
    entries.clear();
336
0
    int nitems = toI(m->linp.npages);
337
0
    load_vector_int(h, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects);
338
0
    load_vector_int(
339
0
        h, nitems, entries, t.nbits_delta_page_length, &HPageOffsetEntry::delta_page_length);
340
0
    load_vector_int(
341
0
        h, nitems, entries, t.nbits_nshared_objects, &HPageOffsetEntry::nshared_objects);
342
0
    load_vector_vector(
343
0
        h,
344
0
        nitems,
345
0
        entries,
346
0
        &HPageOffsetEntry::nshared_objects,
347
0
        t.nbits_shared_identifier,
348
0
        &HPageOffsetEntry::shared_identifiers);
349
0
    load_vector_vector(
350
0
        h,
351
0
        nitems,
352
0
        entries,
353
0
        &HPageOffsetEntry::nshared_objects,
354
0
        t.nbits_shared_numerator,
355
0
        &HPageOffsetEntry::shared_numerators);
356
0
    load_vector_int(
357
0
        h, nitems, entries, t.nbits_delta_content_offset, &HPageOffsetEntry::delta_content_offset);
358
0
    load_vector_int(
359
0
        h, nitems, entries, t.nbits_delta_content_length, &HPageOffsetEntry::delta_content_length);
360
0
}
361
362
void
363
QPDF::readHSharedObject(BitStream h)
364
0
{
365
0
    HSharedObject& t = m->shared_object_hints;
366
367
0
    t.first_shared_obj = h.getBitsInt(32);         // 1
368
0
    t.first_shared_offset = h.getBitsInt(32);      // 2
369
0
    t.nshared_first_page = h.getBitsInt(32);       // 3
370
0
    t.nshared_total = h.getBitsInt(32);            // 4
371
0
    t.nbits_nobjects = h.getBitsInt(16);           // 5
372
0
    t.min_group_length = h.getBitsInt(32);         // 6
373
0
    t.nbits_delta_group_length = h.getBitsInt(16); // 7
374
375
0
    QTC::TC(
376
0
        "qpdf",
377
0
        "QPDF lin nshared_total > nshared_first_page",
378
0
        (t.nshared_total > t.nshared_first_page) ? 1 : 0);
379
380
0
    std::vector<HSharedObjectEntry>& entries = t.entries;
381
0
    entries.clear();
382
0
    int nitems = t.nshared_total;
383
0
    load_vector_int(
384
0
        h, nitems, entries, t.nbits_delta_group_length, &HSharedObjectEntry::delta_group_length);
385
0
    load_vector_int(h, nitems, entries, 1, &HSharedObjectEntry::signature_present);
386
0
    for (size_t i = 0; i < toS(nitems); ++i) {
387
0
        if (entries.at(i).signature_present) {
388
            // Skip 128-bit MD5 hash.  These are not supported by acrobat, so they should probably
389
            // never be there.  We have no test case for this.
390
0
            for (int j = 0; j < 4; ++j) {
391
0
                (void)h.getBits(32);
392
0
            }
393
0
        }
394
0
    }
395
0
    load_vector_int(h, nitems, entries, t.nbits_nobjects, &HSharedObjectEntry::nobjects_minus_one);
396
0
}
397
398
void
399
QPDF::readHGeneric(BitStream h, HGeneric& t)
400
0
{
401
0
    t.first_object = h.getBitsInt(32);        // 1
402
0
    t.first_object_offset = h.getBitsInt(32); // 2
403
0
    t.nobjects = h.getBitsInt(32);            // 3
404
0
    t.group_length = h.getBitsInt(32);        // 4
405
0
}
406
407
void
408
QPDF::checkLinearizationInternal()
409
0
{
410
    // All comments referring to the PDF spec refer to the spec for version 1.4.
411
412
    // Check all values in linearization parameter dictionary
413
414
0
    LinParameters& p = m->linp;
415
416
    // L: file size in bytes -- checked by isLinearized
417
418
    // O: object number of first page
419
0
    std::vector<QPDFObjectHandle> const& pages = getAllPages();
420
0
    if (p.first_page_object != pages.at(0).getObjectID()) {
421
0
        QTC::TC("qpdf", "QPDF err /O mismatch");
422
0
        linearizationWarning("first page object (/O) mismatch");
423
0
    }
424
425
    // N: number of pages
426
0
    size_t npages = pages.size();
427
0
    if (std::cmp_not_equal(p.npages, npages)) {
428
        // Not tested in the test suite
429
0
        linearizationWarning("page count (/N) mismatch");
430
0
    }
431
432
0
    for (size_t i = 0; i < npages; ++i) {
433
0
        QPDFObjectHandle const& page = pages.at(i);
434
0
        QPDFObjGen og(page.getObjGen());
435
0
        if (m->xref_table[og].getType() == 2) {
436
0
            linearizationWarning(
437
0
                "page dictionary for page " + std::to_string(i) + " is compressed");
438
0
        }
439
0
    }
440
441
    // T: offset of whitespace character preceding xref entry for object 0
442
0
    m->file->seek(p.xref_zero_offset, SEEK_SET);
443
0
    while (true) {
444
0
        char ch;
445
0
        m->file->read(&ch, 1);
446
0
        if (!((ch == ' ') || (ch == '\r') || (ch == '\n'))) {
447
0
            m->file->seek(-1, SEEK_CUR);
448
0
            break;
449
0
        }
450
0
    }
451
0
    if (m->file->tell() != m->first_xref_item_offset) {
452
0
        QTC::TC("qpdf", "QPDF err /T mismatch");
453
0
        linearizationWarning(
454
0
            "space before first xref item (/T) mismatch (computed = " +
455
0
            std::to_string(m->first_xref_item_offset) +
456
0
            "; file = " + std::to_string(m->file->tell()));
457
0
    }
458
459
    // P: first page number -- Implementation note 124 says Acrobat ignores this value, so we will
460
    // too.
461
462
    // Check numbering of compressed objects in each xref section. For linearized files, all
463
    // compressed objects are supposed to be at the end of the containing xref section if any object
464
    // streams are in use.
465
466
0
    if (m->uncompressed_after_compressed) {
467
0
        linearizationWarning(
468
0
            "linearized file contains an uncompressed object after a compressed "
469
0
            "one in a cross-reference stream");
470
0
    }
471
472
    // Further checking requires optimization and order calculation. Don't allow optimization to
473
    // make changes.  If it has to, then the file is not properly linearized.  We use the xref table
474
    // to figure out which objects are compressed and which are uncompressed.
475
0
    { // local scope
476
0
        std::map<int, int> object_stream_data;
477
0
        for (auto const& iter: m->xref_table) {
478
0
            QPDFObjGen const& og = iter.first;
479
0
            QPDFXRefEntry const& entry = iter.second;
480
0
            if (entry.getType() == 2) {
481
0
                object_stream_data[og.getObj()] = entry.getObjStreamNumber();
482
0
            }
483
0
        }
484
0
        optimize_internal(object_stream_data, false, nullptr);
485
0
        calculateLinearizationData(object_stream_data);
486
0
    }
487
488
    // E: offset of end of first page -- Implementation note 123 says Acrobat includes on extra
489
    // object here by mistake.  pdlin fails to place thumbnail images in section 9, so when
490
    // thumbnails are present, it also gets the wrong value for /E.  It also doesn't count outlines
491
    // here when it should even though it places them in part 6.  This code fails to put thread
492
    // information dictionaries in part 9, so it actually gets the wrong value for E when threads
493
    // are present.  In that case, it would probably agree with pdlin.  As of this writing, the test
494
    // suite doesn't contain any files with threads.
495
496
0
    if (m->part6.empty()) {
497
0
        stopOnError("linearization part 6 unexpectedly empty");
498
0
    }
499
0
    qpdf_offset_t min_E = -1;
500
0
    qpdf_offset_t max_E = -1;
501
0
    for (auto const& oh: m->part6) {
502
0
        QPDFObjGen og(oh.getObjGen());
503
0
        if (!m->obj_cache.contains(og)) {
504
            // All objects have to have been dereferenced to be classified.
505
0
            throw std::logic_error("linearization part6 object not in cache");
506
0
        }
507
0
        ObjCache const& oc = m->obj_cache[og];
508
0
        min_E = std::max(min_E, oc.end_before_space);
509
0
        max_E = std::max(max_E, oc.end_after_space);
510
0
    }
511
0
    if ((p.first_page_end < min_E) || (p.first_page_end > max_E)) {
512
0
        QTC::TC("qpdf", "QPDF warn /E mismatch");
513
0
        linearizationWarning(
514
0
            "end of first page section (/E) mismatch: /E = " + std::to_string(p.first_page_end) +
515
0
            "; computed = " + std::to_string(min_E) + ".." + std::to_string(max_E));
516
0
    }
517
518
    // Check hint tables
519
520
0
    std::map<int, int> shared_idx_to_obj;
521
0
    checkHSharedObject(pages, shared_idx_to_obj);
522
0
    checkHPageOffset(pages, shared_idx_to_obj);
523
0
    checkHOutlines();
524
0
}
525
526
qpdf_offset_t
527
QPDF::maxEnd(ObjUser const& ou)
528
0
{
529
0
    if (!m->obj_user_to_objects.contains(ou)) {
530
0
        stopOnError("no entry in object user table for requested object user");
531
0
    }
532
0
    qpdf_offset_t end = 0;
533
0
    for (auto const& og: m->obj_user_to_objects[ou]) {
534
0
        if (!m->obj_cache.contains(og)) {
535
0
            stopOnError("unknown object referenced in object user table");
536
0
        }
537
0
        end = std::max(end, m->obj_cache[og].end_after_space);
538
0
    }
539
0
    return end;
540
0
}
541
542
qpdf_offset_t
543
QPDF::getLinearizationOffset(QPDFObjGen og)
544
0
{
545
0
    QPDFXRefEntry entry = m->xref_table[og];
546
0
    qpdf_offset_t result = 0;
547
0
    switch (entry.getType()) {
548
0
    case 1:
549
0
        result = entry.getOffset();
550
0
        break;
551
552
0
    case 2:
553
        // For compressed objects, return the offset of the object stream that contains them.
554
0
        result = getLinearizationOffset(QPDFObjGen(entry.getObjStreamNumber(), 0));
555
0
        break;
556
557
0
    default:
558
0
        stopOnError("getLinearizationOffset called for xref entry not of type 1 or 2");
559
0
        break;
560
0
    }
561
0
    return result;
562
0
}
563
564
QPDFObjectHandle
565
QPDF::getUncompressedObject(QPDFObjectHandle& obj, std::map<int, int> const& object_stream_data)
566
0
{
567
0
    if (obj.null() || (!object_stream_data.contains(obj.getObjectID()))) {
568
0
        return obj;
569
0
    } else {
570
0
        int repl = (*(object_stream_data.find(obj.getObjectID()))).second;
571
0
        return getObject(repl, 0);
572
0
    }
573
0
}
574
575
QPDFObjectHandle
576
QPDF::getUncompressedObject(QPDFObjectHandle& oh, QPDFWriter::ObjTable const& obj)
577
28.5k
{
578
28.5k
    if (obj.contains(oh)) {
579
28.5k
        if (auto id = obj[oh].object_stream; id > 0) {
580
85
            return oh.isNull() ? oh : getObject(id, 0);
581
85
        }
582
28.5k
    }
583
28.4k
    return oh;
584
28.5k
}
585
586
int
587
QPDF::lengthNextN(int first_object, int n)
588
0
{
589
0
    int length = 0;
590
0
    for (int i = 0; i < n; ++i) {
591
0
        QPDFObjGen og(first_object + i, 0);
592
0
        if (!m->xref_table.contains(og)) {
593
0
            linearizationWarning(
594
0
                "no xref table entry for " + std::to_string(first_object + i) + " 0");
595
0
        } else {
596
0
            if (!m->obj_cache.contains(og)) {
597
0
                stopOnError("found unknown object while calculating length for linearization data");
598
0
            }
599
0
            length += toI(m->obj_cache[og].end_after_space - getLinearizationOffset(og));
600
0
        }
601
0
    }
602
0
    return length;
603
0
}
604
605
void
606
QPDF::checkHPageOffset(
607
    std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& shared_idx_to_obj)
608
0
{
609
    // Implementation note 126 says Acrobat always sets delta_content_offset and
610
    // delta_content_length in the page offset header dictionary to 0.  It also states that
611
    // min_content_offset in the per-page information is always 0, which is an incorrect value.
612
613
    // Implementation note 127 explains that Acrobat always sets item 8 (min_content_length) to
614
    // zero, item 9 (nbits_delta_content_length) to the value of item 5 (nbits_delta_page_length),
615
    // and item 7 of each per-page hint table (delta_content_length) to item 2 (delta_page_length)
616
    // of that entry.  Acrobat ignores these values when reading files.
617
618
    // Empirically, it also seems that Acrobat sometimes puts items under a page's /Resources
619
    // dictionary in with shared objects even when they are private.
620
621
0
    size_t npages = pages.size();
622
0
    qpdf_offset_t table_offset = adjusted_offset(m->page_offset_hints.first_page_offset);
623
0
    QPDFObjGen first_page_og(pages.at(0).getObjGen());
624
0
    if (!m->xref_table.contains(first_page_og)) {
625
0
        stopOnError("supposed first page object is not known");
626
0
    }
627
0
    qpdf_offset_t offset = getLinearizationOffset(first_page_og);
628
0
    if (table_offset != offset) {
629
0
        linearizationWarning("first page object offset mismatch");
630
0
    }
631
632
0
    for (size_t pageno = 0; pageno < npages; ++pageno) {
633
0
        QPDFObjGen page_og(pages.at(pageno).getObjGen());
634
0
        int first_object = page_og.getObj();
635
0
        if (!m->xref_table.contains(page_og)) {
636
0
            stopOnError("unknown object in page offset hint table");
637
0
        }
638
0
        offset = getLinearizationOffset(page_og);
639
640
0
        HPageOffsetEntry& he = m->page_offset_hints.entries.at(toS(pageno));
641
0
        CHPageOffsetEntry& ce = m->c_page_offset_data.entries.at(toS(pageno));
642
0
        int h_nobjects = he.delta_nobjects + m->page_offset_hints.min_nobjects;
643
0
        if (h_nobjects != ce.nobjects) {
644
            // This happens with pdlin when there are thumbnails.
645
0
            linearizationWarning(
646
0
                "object count mismatch for page " + std::to_string(pageno) + ": hint table = " +
647
0
                std::to_string(h_nobjects) + "; computed = " + std::to_string(ce.nobjects));
648
0
        }
649
650
        // Use value for number of objects in hint table rather than computed value if there is a
651
        // discrepancy.
652
0
        int length = lengthNextN(first_object, h_nobjects);
653
0
        int h_length = toI(he.delta_page_length + m->page_offset_hints.min_page_length);
654
0
        if (length != h_length) {
655
            // This condition almost certainly indicates a bad hint table or a bug in this code.
656
0
            linearizationWarning(
657
0
                "page length mismatch for page " + std::to_string(pageno) + ": hint table = " +
658
0
                std::to_string(h_length) + "; computed length = " + std::to_string(length) +
659
0
                " (offset = " + std::to_string(offset) + ")");
660
0
        }
661
662
0
        offset += h_length;
663
664
        // Translate shared object indexes to object numbers.
665
0
        std::set<int> hint_shared;
666
0
        std::set<int> computed_shared;
667
668
0
        if ((pageno == 0) && (he.nshared_objects > 0)) {
669
            // pdlin and Acrobat both do this even though the spec states clearly and unambiguously
670
            // that they should not.
671
0
            linearizationWarning("page 0 has shared identifier entries");
672
0
        }
673
674
0
        for (size_t i = 0; i < toS(he.nshared_objects); ++i) {
675
0
            int idx = he.shared_identifiers.at(i);
676
0
            if (!shared_idx_to_obj.contains(idx)) {
677
0
                stopOnError("unable to get object for item in shared objects hint table");
678
0
            }
679
0
            hint_shared.insert(shared_idx_to_obj[idx]);
680
0
        }
681
682
0
        for (size_t i = 0; i < toS(ce.nshared_objects); ++i) {
683
0
            int idx = ce.shared_identifiers.at(i);
684
0
            if (idx >= m->c_shared_object_data.nshared_total) {
685
0
                stopOnError("index out of bounds for shared object hint table");
686
0
            }
687
0
            int obj = m->c_shared_object_data.entries.at(toS(idx)).object;
688
0
            computed_shared.insert(obj);
689
0
        }
690
691
0
        for (int iter: hint_shared) {
692
0
            if (!computed_shared.contains(iter)) {
693
                // pdlin puts thumbnails here even though it shouldn't
694
0
                linearizationWarning(
695
0
                    "page " + std::to_string(pageno) + ": shared object " + std::to_string(iter) +
696
0
                    ": in hint table but not computed list");
697
0
            }
698
0
        }
699
700
0
        for (int iter: computed_shared) {
701
0
            if (!hint_shared.contains(iter)) {
702
                // Acrobat does not put some things including at least built-in fonts and procsets
703
                // here, at least in some cases.
704
0
                linearizationWarning(
705
0
                    ("page " + std::to_string(pageno) + ": shared object " + std::to_string(iter) +
706
0
                     ": in computed list but not hint table"));
707
0
            }
708
0
        }
709
0
    }
710
0
}
711
712
void
713
QPDF::checkHSharedObject(std::vector<QPDFObjectHandle> const& pages, std::map<int, int>& idx_to_obj)
714
0
{
715
    // Implementation note 125 says shared object groups always contain only one object.
716
    // Implementation note 128 says that Acrobat always nbits_nobjects to zero.  Implementation note
717
    // 130 says that Acrobat does not support more than one shared object per group.  These are all
718
    // consistent.
719
720
    // Implementation note 129 states that MD5 signatures are not implemented in Acrobat, so
721
    // signature_present must always be zero.
722
723
    // Implementation note 131 states that first_shared_obj and first_shared_offset have meaningless
724
    // values for single-page files.
725
726
    // Empirically, Acrobat and pdlin generate incorrect values for these whenever there are no
727
    // shared objects not referenced by the first page (i.e., nshared_total == nshared_first_page).
728
729
0
    HSharedObject& so = m->shared_object_hints;
730
0
    if (so.nshared_total < so.nshared_first_page) {
731
0
        linearizationWarning("shared object hint table: ntotal < nfirst_page");
732
0
    } else {
733
        // The first nshared_first_page objects are consecutive objects starting with the first page
734
        // object.  The rest are consecutive starting from the first_shared_obj object.
735
0
        int cur_object = pages.at(0).getObjectID();
736
0
        for (int i = 0; i < so.nshared_total; ++i) {
737
0
            if (i == so.nshared_first_page) {
738
0
                QTC::TC("qpdf", "QPDF lin check shared past first page");
739
0
                if (m->part8.empty()) {
740
0
                    linearizationWarning("part 8 is empty but nshared_total > nshared_first_page");
741
0
                } else {
742
0
                    int obj = m->part8.at(0).getObjectID();
743
0
                    if (obj != so.first_shared_obj) {
744
0
                        linearizationWarning(
745
0
                            "first shared object number mismatch: hint table = " +
746
0
                            std::to_string(so.first_shared_obj) +
747
0
                            "; computed = " + std::to_string(obj));
748
0
                    }
749
0
                }
750
751
0
                cur_object = so.first_shared_obj;
752
753
0
                QPDFObjGen og(cur_object, 0);
754
0
                if (!m->xref_table.contains(og)) {
755
0
                    stopOnError("unknown object in shared object hint table");
756
0
                }
757
0
                qpdf_offset_t offset = getLinearizationOffset(og);
758
0
                qpdf_offset_t h_offset = adjusted_offset(so.first_shared_offset);
759
0
                if (offset != h_offset) {
760
0
                    linearizationWarning(
761
0
                        "first shared object offset mismatch: hint table = " +
762
0
                        std::to_string(h_offset) + "; computed = " + std::to_string(offset));
763
0
                }
764
0
            }
765
766
0
            idx_to_obj[i] = cur_object;
767
0
            HSharedObjectEntry& se = so.entries.at(toS(i));
768
0
            int nobjects = se.nobjects_minus_one + 1;
769
0
            int length = lengthNextN(cur_object, nobjects);
770
0
            int h_length = so.min_group_length + se.delta_group_length;
771
0
            if (length != h_length) {
772
0
                linearizationWarning(
773
0
                    "shared object " + std::to_string(i) + " length mismatch: hint table = " +
774
0
                    std::to_string(h_length) + "; computed = " + std::to_string(length));
775
0
            }
776
0
            cur_object += nobjects;
777
0
        }
778
0
    }
779
0
}
780
781
void
782
QPDF::checkHOutlines()
783
0
{
784
    // Empirically, Acrobat generates the correct value for the object number but incorrectly stores
785
    // the next object number's offset as the offset, at least when outlines appear in part 6.  It
786
    // also generates an incorrect value for length (specifically, the length that would cover the
787
    // correct number of objects from the wrong starting place).  pdlin appears to generate correct
788
    // values in those cases.
789
790
0
    if (m->c_outline_data.nobjects == m->outline_hints.nobjects) {
791
0
        if (m->c_outline_data.nobjects == 0) {
792
0
            return;
793
0
        }
794
795
0
        if (m->c_outline_data.first_object == m->outline_hints.first_object) {
796
            // Check length and offset.  Acrobat gets these wrong.
797
0
            QPDFObjectHandle outlines = getRoot().getKey("/Outlines");
798
0
            if (!outlines.isIndirect()) {
799
                // This case is not exercised in test suite since not permitted by the spec, but if
800
                // this does occur, the code below would fail.
801
0
                linearizationWarning("/Outlines key of root dictionary is not indirect");
802
0
                return;
803
0
            }
804
0
            QPDFObjGen og(outlines.getObjGen());
805
0
            if (!m->xref_table.contains(og)) {
806
0
                stopOnError("unknown object in outlines hint table");
807
0
            }
808
0
            qpdf_offset_t offset = getLinearizationOffset(og);
809
0
            ObjUser ou(ObjUser::ou_root_key, "/Outlines");
810
0
            int length = toI(maxEnd(ou) - offset);
811
0
            qpdf_offset_t table_offset = adjusted_offset(m->outline_hints.first_object_offset);
812
0
            if (offset != table_offset) {
813
0
                linearizationWarning(
814
0
                    "incorrect offset in outlines table: hint table = " +
815
0
                    std::to_string(table_offset) + "; computed = " + std::to_string(offset));
816
0
            }
817
0
            int table_length = m->outline_hints.group_length;
818
0
            if (length != table_length) {
819
0
                linearizationWarning(
820
0
                    "incorrect length in outlines table: hint table = " +
821
0
                    std::to_string(table_length) + "; computed = " + std::to_string(length));
822
0
            }
823
0
        } else {
824
0
            linearizationWarning("incorrect first object number in outline hints table.");
825
0
        }
826
0
    } else {
827
0
        linearizationWarning("incorrect object count in outline hint table");
828
0
    }
829
0
}
830
831
void
832
QPDF::showLinearizationData()
833
0
{
834
0
    try {
835
0
        readLinearizationData();
836
0
        checkLinearizationInternal();
837
0
        dumpLinearizationDataInternal();
838
0
    } catch (QPDFExc& e) {
839
0
        linearizationWarning(e.what());
840
0
    }
841
0
}
842
843
void
844
QPDF::dumpLinearizationDataInternal()
845
0
{
846
0
    *m->log->getInfo() << m->file->getName() << ": linearization data:\n\n";
847
848
0
    *m->log->getInfo() << "file_size: " << m->linp.file_size << "\n"
849
0
                       << "first_page_object: " << m->linp.first_page_object << "\n"
850
0
                       << "first_page_end: " << m->linp.first_page_end << "\n"
851
0
                       << "npages: " << m->linp.npages << "\n"
852
0
                       << "xref_zero_offset: " << m->linp.xref_zero_offset << "\n"
853
0
                       << "first_page: " << m->linp.first_page << "\n"
854
0
                       << "H_offset: " << m->linp.H_offset << "\n"
855
0
                       << "H_length: " << m->linp.H_length << "\n"
856
0
                       << "\n";
857
858
0
    *m->log->getInfo() << "Page Offsets Hint Table\n\n";
859
0
    dumpHPageOffset();
860
0
    *m->log->getInfo() << "\nShared Objects Hint Table\n\n";
861
0
    dumpHSharedObject();
862
863
0
    if (m->outline_hints.nobjects > 0) {
864
0
        *m->log->getInfo() << "\nOutlines Hint Table\n\n";
865
0
        dumpHGeneric(m->outline_hints);
866
0
    }
867
0
}
868
869
qpdf_offset_t
870
QPDF::adjusted_offset(qpdf_offset_t offset)
871
0
{
872
    // All offsets >= H_offset have to be increased by H_length since all hint table location values
873
    // disregard the hint table itself.
874
0
    if (offset >= m->linp.H_offset) {
875
0
        return offset + m->linp.H_length;
876
0
    }
877
0
    return offset;
878
0
}
879
880
void
881
QPDF::dumpHPageOffset()
882
0
{
883
0
    HPageOffset& t = m->page_offset_hints;
884
0
    *m->log->getInfo() << "min_nobjects: " << t.min_nobjects << "\n"
885
0
                       << "first_page_offset: " << adjusted_offset(t.first_page_offset) << "\n"
886
0
                       << "nbits_delta_nobjects: " << t.nbits_delta_nobjects << "\n"
887
0
                       << "min_page_length: " << t.min_page_length << "\n"
888
0
                       << "nbits_delta_page_length: " << t.nbits_delta_page_length << "\n"
889
0
                       << "min_content_offset: " << t.min_content_offset << "\n"
890
0
                       << "nbits_delta_content_offset: " << t.nbits_delta_content_offset << "\n"
891
0
                       << "min_content_length: " << t.min_content_length << "\n"
892
0
                       << "nbits_delta_content_length: " << t.nbits_delta_content_length << "\n"
893
0
                       << "nbits_nshared_objects: " << t.nbits_nshared_objects << "\n"
894
0
                       << "nbits_shared_identifier: " << t.nbits_shared_identifier << "\n"
895
0
                       << "nbits_shared_numerator: " << t.nbits_shared_numerator << "\n"
896
0
                       << "shared_denominator: " << t.shared_denominator << "\n";
897
898
0
    for (size_t i1 = 0; i1 < m->linp.npages; ++i1) {
899
0
        HPageOffsetEntry& pe = t.entries.at(i1);
900
0
        *m->log->getInfo() << "Page " << i1 << ":\n"
901
0
                           << "  nobjects: " << pe.delta_nobjects + t.min_nobjects << "\n"
902
0
                           << "  length: " << pe.delta_page_length + t.min_page_length
903
0
                           << "\n"
904
                           // content offset is relative to page, not file
905
0
                           << "  content_offset: " << pe.delta_content_offset + t.min_content_offset
906
0
                           << "\n"
907
0
                           << "  content_length: " << pe.delta_content_length + t.min_content_length
908
0
                           << "\n"
909
0
                           << "  nshared_objects: " << pe.nshared_objects << "\n";
910
0
        for (size_t i2 = 0; i2 < toS(pe.nshared_objects); ++i2) {
911
0
            *m->log->getInfo() << "    identifier " << i2 << ": " << pe.shared_identifiers.at(i2)
912
0
                               << "\n";
913
0
            *m->log->getInfo() << "    numerator " << i2 << ": " << pe.shared_numerators.at(i2)
914
0
                               << "\n";
915
0
        }
916
0
    }
917
0
}
918
919
void
920
QPDF::dumpHSharedObject()
921
0
{
922
0
    HSharedObject& t = m->shared_object_hints;
923
0
    *m->log->getInfo() << "first_shared_obj: " << t.first_shared_obj << "\n"
924
0
                       << "first_shared_offset: " << adjusted_offset(t.first_shared_offset) << "\n"
925
0
                       << "nshared_first_page: " << t.nshared_first_page << "\n"
926
0
                       << "nshared_total: " << t.nshared_total << "\n"
927
0
                       << "nbits_nobjects: " << t.nbits_nobjects << "\n"
928
0
                       << "min_group_length: " << t.min_group_length << "\n"
929
0
                       << "nbits_delta_group_length: " << t.nbits_delta_group_length << "\n";
930
931
0
    for (size_t i = 0; i < toS(t.nshared_total); ++i) {
932
0
        HSharedObjectEntry& se = t.entries.at(i);
933
0
        *m->log->getInfo() << "Shared Object " << i << ":\n"
934
0
                           << "  group length: " << se.delta_group_length + t.min_group_length
935
0
                           << "\n";
936
        // PDF spec says signature present nobjects_minus_one are always 0, so print them only if
937
        // they have a non-zero value.
938
0
        if (se.signature_present) {
939
0
            *m->log->getInfo() << "  signature present\n";
940
0
        }
941
0
        if (se.nobjects_minus_one != 0) {
942
0
            *m->log->getInfo() << "  nobjects: " << se.nobjects_minus_one + 1 << "\n";
943
0
        }
944
0
    }
945
0
}
946
947
void
948
QPDF::dumpHGeneric(HGeneric& t)
949
0
{
950
0
    *m->log->getInfo() << "first_object: " << t.first_object << "\n"
951
0
                       << "first_object_offset: " << adjusted_offset(t.first_object_offset) << "\n"
952
0
                       << "nobjects: " << t.nobjects << "\n"
953
0
                       << "group_length: " << t.group_length << "\n";
954
0
}
955
956
template <typename T>
957
void
958
QPDF::calculateLinearizationData(T const& object_stream_data)
959
9.10k
{
960
    // This function calculates the ordering of objects, divides them into the appropriate parts,
961
    // and computes some values for the linearization parameter dictionary and hint tables.  The
962
    // file must be optimized (via calling optimize()) prior to calling this function.  Note that
963
    // actual offsets and lengths are not computed here, but anything related to object ordering is.
964
965
9.10k
    if (m->object_to_obj_users.empty()) {
966
        // Note that we can't call optimize here because we don't know whether it should be called
967
        // with or without allow changes.
968
0
        throw std::logic_error(
969
0
            "INTERNAL ERROR: QPDF::calculateLinearizationData called before optimize()");
970
0
    }
971
972
    // Separate objects into the categories sufficient for us to determine which part of the
973
    // linearized file should contain the object.  This categorization is useful for other purposes
974
    // as well.  Part numbers refer to version 1.4 of the PDF spec.
975
976
    // Parts 1, 3, 5, 10, and 11 don't contain any objects from the original file (except the
977
    // trailer dictionary in part 11).
978
979
    // Part 4 is the document catalog (root) and the following root keys: /ViewerPreferences,
980
    // /PageMode, /Threads, /OpenAction, /AcroForm, /Encrypt.  Note that Thread information
981
    // dictionaries are supposed to appear in part 9, but we are disregarding that recommendation
982
    // for now.
983
984
    // Part 6 is the first page section.  It includes all remaining objects referenced by the first
985
    // page including shared objects but not including thumbnails.  Additionally, if /PageMode is
986
    // /Outlines, then information from /Outlines also appears here.
987
988
    // Part 7 contains remaining objects private to pages other than the first page.
989
990
    // Part 8 contains all remaining shared objects except those that are shared only within
991
    // thumbnails.
992
993
    // Part 9 contains all remaining objects.
994
995
    // We sort objects into the following categories:
996
997
    //   * open_document: part 4
998
999
    //   * first_page_private: part 6
1000
1001
    //   * first_page_shared: part 6
1002
1003
    //   * other_page_private: part 7
1004
1005
    //   * other_page_shared: part 8
1006
1007
    //   * thumbnail_private: part 9
1008
1009
    //   * thumbnail_shared: part 9
1010
1011
    //   * other: part 9
1012
1013
    //   * outlines: part 6 or 9
1014
1015
9.10k
    m->part4.clear();
1016
9.10k
    m->part6.clear();
1017
9.10k
    m->part7.clear();
1018
9.10k
    m->part8.clear();
1019
9.10k
    m->part9.clear();
1020
9.10k
    m->c_linp = LinParameters();
1021
9.10k
    m->c_page_offset_data = CHPageOffset();
1022
9.10k
    m->c_shared_object_data = CHSharedObject();
1023
9.10k
    m->c_outline_data = HGeneric();
1024
1025
9.10k
    QPDFObjectHandle root = getRoot();
1026
9.10k
    bool outlines_in_first_page = false;
1027
9.10k
    QPDFObjectHandle pagemode = root.getKey("/PageMode");
1028
9.10k
    QTC::TC("qpdf", "QPDF categorize pagemode present", pagemode.isName() ? 1 : 0);
1029
9.10k
    if (pagemode.isName()) {
1030
331
        if (pagemode.getName() == "/UseOutlines") {
1031
184
            if (root.hasKey("/Outlines")) {
1032
75
                outlines_in_first_page = true;
1033
109
            } else {
1034
109
                QTC::TC("qpdf", "QPDF UseOutlines but no Outlines");
1035
109
            }
1036
184
        }
1037
331
        QTC::TC("qpdf", "QPDF categorize pagemode outlines", outlines_in_first_page ? 1 : 0);
1038
331
    }
1039
1040
9.10k
    std::set<std::string> open_document_keys;
1041
9.10k
    open_document_keys.insert("/ViewerPreferences");
1042
9.10k
    open_document_keys.insert("/PageMode");
1043
9.10k
    open_document_keys.insert("/Threads");
1044
9.10k
    open_document_keys.insert("/OpenAction");
1045
9.10k
    open_document_keys.insert("/AcroForm");
1046
1047
9.10k
    std::set<QPDFObjGen> lc_open_document;
1048
9.10k
    std::set<QPDFObjGen> lc_first_page_private;
1049
9.10k
    std::set<QPDFObjGen> lc_first_page_shared;
1050
9.10k
    std::set<QPDFObjGen> lc_other_page_private;
1051
9.10k
    std::set<QPDFObjGen> lc_other_page_shared;
1052
9.10k
    std::set<QPDFObjGen> lc_thumbnail_private;
1053
9.10k
    std::set<QPDFObjGen> lc_thumbnail_shared;
1054
9.10k
    std::set<QPDFObjGen> lc_other;
1055
9.10k
    std::set<QPDFObjGen> lc_outlines;
1056
9.10k
    std::set<QPDFObjGen> lc_root;
1057
1058
145k
    for (auto& oiter: m->object_to_obj_users) {
1059
145k
        QPDFObjGen const& og = oiter.first;
1060
145k
        std::set<ObjUser>& ous = oiter.second;
1061
1062
145k
        bool in_open_document = false;
1063
145k
        bool in_first_page = false;
1064
145k
        int other_pages = 0;
1065
145k
        int thumbs = 0;
1066
145k
        int others = 0;
1067
145k
        bool in_outlines = false;
1068
145k
        bool is_root = false;
1069
1070
301k
        for (auto const& ou: ous) {
1071
301k
            switch (ou.ou_type) {
1072
45.4k
            case ObjUser::ou_trailer_key:
1073
45.4k
                if (ou.key == "/Encrypt") {
1074
732
                    in_open_document = true;
1075
44.7k
                } else {
1076
44.7k
                    ++others;
1077
44.7k
                }
1078
45.4k
                break;
1079
1080
6.36k
            case ObjUser::ou_thumb:
1081
6.36k
                ++thumbs;
1082
6.36k
                break;
1083
1084
97.2k
            case ObjUser::ou_root_key:
1085
97.2k
                if (open_document_keys.contains(ou.key)) {
1086
16.6k
                    in_open_document = true;
1087
80.5k
                } else if (ou.key == "/Outlines") {
1088
3.88k
                    in_outlines = true;
1089
76.6k
                } else {
1090
76.6k
                    ++others;
1091
76.6k
                }
1092
97.2k
                break;
1093
1094
143k
            case ObjUser::ou_page:
1095
143k
                if (ou.pageno == 0) {
1096
69.7k
                    in_first_page = true;
1097
73.3k
                } else {
1098
73.3k
                    ++other_pages;
1099
73.3k
                }
1100
143k
                break;
1101
1102
9.10k
            case ObjUser::ou_root:
1103
9.10k
                is_root = true;
1104
9.10k
                break;
1105
301k
            }
1106
301k
        }
1107
1108
145k
        if (is_root) {
1109
9.10k
            lc_root.insert(og);
1110
135k
        } else if (in_outlines) {
1111
3.86k
            lc_outlines.insert(og);
1112
132k
        } else if (in_open_document) {
1113
17.3k
            lc_open_document.insert(og);
1114
114k
        } else if ((in_first_page) && (others == 0) && (other_pages == 0) && (thumbs == 0)) {
1115
50.8k
            lc_first_page_private.insert(og);
1116
63.8k
        } else if (in_first_page) {
1117
14.7k
            lc_first_page_shared.insert(og);
1118
49.1k
        } else if ((other_pages == 1) && (others == 0) && (thumbs == 0)) {
1119
12.4k
            lc_other_page_private.insert(og);
1120
36.6k
        } else if (other_pages > 1) {
1121
2.62k
            lc_other_page_shared.insert(og);
1122
34.0k
        } else if ((thumbs == 1) && (others == 0)) {
1123
2.17k
            lc_thumbnail_private.insert(og);
1124
31.8k
        } else if (thumbs > 1) {
1125
1.21k
            lc_thumbnail_shared.insert(og);
1126
30.6k
        } else {
1127
30.6k
            lc_other.insert(og);
1128
30.6k
        }
1129
145k
    }
1130
1131
    // Generate ordering for objects in the output file.  Sometimes we just dump right from a set
1132
    // into a vector.  Rather than optimizing this by going straight into the vector, we'll leave
1133
    // these phases separate for now.  That way, this section can be concerned only with ordering,
1134
    // and the above section can be considered only with categorization.  Note that sets of
1135
    // QPDFObjGens are sorted by QPDFObjGen.  In a linearized file, objects appear in sequence with
1136
    // the possible exception of hints tables which we won't see here anyway.  That means that
1137
    // running calculateLinearizationData() on a linearized file should give results identical to
1138
    // the original file ordering.
1139
1140
    // We seem to traverse the page tree a lot in this code, but we can address this for a future
1141
    // code optimization if necessary. Premature optimization is the root of all evil.
1142
9.10k
    std::vector<QPDFObjectHandle> pages;
1143
9.10k
    { // local scope
1144
        // Map all page objects to the containing object stream.  This should be a no-op in a
1145
        // properly linearized file.
1146
14.5k
        for (auto oh: getAllPages()) {
1147
14.5k
            pages.push_back(getUncompressedObject(oh, object_stream_data));
1148
14.5k
        }
1149
9.10k
    }
1150
9.10k
    size_t npages = pages.size();
1151
1152
    // We will be initializing some values of the computed hint tables.  Specifically, we can
1153
    // initialize any items that deal with object numbers or counts but not any items that deal with
1154
    // lengths or offsets.  The code that writes linearized files will have to fill in these values
1155
    // during the first pass.  The validation code can compute them relatively easily given the rest
1156
    // of the information.
1157
1158
    // npages is the size of the existing pages vector, which has been created by traversing the
1159
    // pages tree, and as such is a reasonable size.
1160
9.10k
    m->c_linp.npages = npages;
1161
9.10k
    m->c_page_offset_data.entries = std::vector<CHPageOffsetEntry>(npages);
1162
1163
    // Part 4: open document objects.  We don't care about the order.
1164
1165
9.10k
    if (lc_root.size() != 1) {
1166
0
        stopOnError("found other than one root while calculating linearization data");
1167
0
    }
1168
9.10k
    m->part4.push_back(getObject(*(lc_root.begin())));
1169
17.3k
    for (auto const& og: lc_open_document) {
1170
17.3k
        m->part4.push_back(getObject(og));
1171
17.3k
    }
1172
1173
    // Part 6: first page objects.  Note: implementation note 124 states that Acrobat always treats
1174
    // page 0 as the first page for linearization regardless of /OpenAction.  pdlin doesn't provide
1175
    // any option to set this and also disregards /OpenAction.  We will do the same.
1176
1177
    // First, place the actual first page object itself.
1178
9.10k
    if (pages.empty()) {
1179
59
        stopOnError("no pages found while calculating linearization data");
1180
59
    }
1181
9.10k
    QPDFObjGen first_page_og(pages.at(0).getObjGen());
1182
9.10k
    if (!lc_first_page_private.contains(first_page_og)) {
1183
495
        stopOnError(
1184
495
            "INTERNAL ERROR: QPDF::calculateLinearizationData: first page "
1185
495
            "object not in lc_first_page_private");
1186
495
    }
1187
9.10k
    lc_first_page_private.erase(first_page_og);
1188
9.10k
    m->c_linp.first_page_object = pages.at(0).getObjectID();
1189
9.10k
    m->part6.push_back(pages.at(0));
1190
1191
    // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard
1192
    // it except to the extent that it groups private and shared objects contiguously for the sake
1193
    // of hint tables.
1194
1195
42.2k
    for (auto const& og: lc_first_page_private) {
1196
42.2k
        m->part6.push_back(getObject(og));
1197
42.2k
    }
1198
1199
9.27k
    for (auto const& og: lc_first_page_shared) {
1200
9.27k
        m->part6.push_back(getObject(og));
1201
9.27k
    }
1202
1203
    // Place the outline dictionary if it goes in the first page section.
1204
9.10k
    if (outlines_in_first_page) {
1205
75
        pushOutlinesToPart(m->part6, lc_outlines, object_stream_data);
1206
75
    }
1207
1208
    // Fill in page offset hint table information for the first page. The PDF spec says that
1209
    // nshared_objects should be zero for the first page.  pdlin does not appear to obey this, but
1210
    // it fills in garbage values for all the shared object identifiers on the first page.
1211
1212
9.10k
    m->c_page_offset_data.entries.at(0).nobjects = toI(m->part6.size());
1213
1214
    // Part 7: other pages' private objects
1215
1216
    // For each page in order:
1217
14.4k
    for (size_t i = 1; i < npages; ++i) {
1218
        // Place this page's page object
1219
1220
5.33k
        QPDFObjGen page_og(pages.at(i).getObjGen());
1221
5.33k
        if (!lc_other_page_private.contains(page_og)) {
1222
58
            stopOnError(
1223
58
                "INTERNAL ERROR: QPDF::calculateLinearizationData: page object for page " +
1224
58
                std::to_string(i) + " not in lc_other_page_private");
1225
58
        }
1226
5.33k
        lc_other_page_private.erase(page_og);
1227
5.33k
        m->part7.push_back(pages.at(i));
1228
1229
        // Place all non-shared objects referenced by this page, updating the page object count for
1230
        // the hint table.
1231
1232
5.33k
        m->c_page_offset_data.entries.at(i).nobjects = 1;
1233
1234
5.33k
        ObjUser ou(ObjUser::ou_page, i);
1235
5.33k
        if (!m->obj_user_to_objects.contains(ou)) {
1236
0
            stopOnError("found unreferenced page while calculating linearization data");
1237
0
        }
1238
71.8k
        for (auto const& og: m->obj_user_to_objects[ou]) {
1239
71.8k
            if (lc_other_page_private.contains(og)) {
1240
6.97k
                lc_other_page_private.erase(og);
1241
6.97k
                m->part7.push_back(getObject(og));
1242
6.97k
                ++m->c_page_offset_data.entries.at(i).nobjects;
1243
6.97k
            }
1244
71.8k
        }
1245
5.33k
    }
1246
    // That should have covered all part7 objects.
1247
9.10k
    if (!lc_other_page_private.empty()) {
1248
0
        stopOnError(
1249
0
            "INTERNAL ERROR: QPDF::calculateLinearizationData:"
1250
0
            " lc_other_page_private is not empty after generation of part7");
1251
0
    }
1252
1253
    // Part 8: other pages' shared objects
1254
1255
    // Order is unimportant.
1256
9.10k
    for (auto const& og: lc_other_page_shared) {
1257
2.59k
        m->part8.push_back(getObject(og));
1258
2.59k
    }
1259
1260
    // Part 9: other objects
1261
1262
    // The PDF specification makes recommendations on ordering here. We follow them only to a
1263
    // limited extent.  Specifically, we put the pages tree first, then private thumbnail objects in
1264
    // page order, then shared thumbnail objects, and then outlines (unless in part 6).  After that,
1265
    // we throw all remaining objects in arbitrary order.
1266
1267
    // Place the pages tree.
1268
9.10k
    std::set<QPDFObjGen> pages_ogs =
1269
9.10k
        m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")];
1270
9.10k
    if (pages_ogs.empty()) {
1271
21
        stopOnError("found empty pages tree while calculating linearization data");
1272
21
    }
1273
11.8k
    for (auto const& og: pages_ogs) {
1274
11.8k
        if (lc_other.contains(og)) {
1275
9.75k
            lc_other.erase(og);
1276
9.75k
            m->part9.push_back(getObject(og));
1277
9.75k
        }
1278
11.8k
    }
1279
1280
    // Place private thumbnail images in page order.  Slightly more information would be required if
1281
    // we were going to bother with thumbnail hint tables.
1282
22.8k
    for (size_t i = 0; i < npages; ++i) {
1283
13.7k
        QPDFObjectHandle thumb = pages.at(i).getKey("/Thumb");
1284
13.7k
        thumb = getUncompressedObject(thumb, object_stream_data);
1285
13.7k
        QPDFObjGen thumb_og(thumb.getObjGen());
1286
        // Output the thumbnail itself
1287
13.7k
        if (lc_thumbnail_private.erase(thumb_og) && !thumb.isNull()) {
1288
310
            m->part9.emplace_back(thumb);
1289
13.3k
        } else {
1290
            // No internal error this time...there's nothing to stop this object from having
1291
            // been referred to somewhere else outside of a page's /Thumb, and if it had been,
1292
            // there's nothing to prevent it from having been in some set other than
1293
            // lc_thumbnail_private.
1294
13.3k
        }
1295
13.7k
        std::set<QPDFObjGen>& ogs = m->obj_user_to_objects[ObjUser(ObjUser::ou_thumb, i)];
1296
13.7k
        for (auto const& og: ogs) {
1297
5.65k
            if (lc_thumbnail_private.erase(og)) {
1298
1.79k
                m->part9.emplace_back(getObject(og));
1299
1.79k
            }
1300
5.65k
        }
1301
13.7k
    }
1302
9.10k
    if (!lc_thumbnail_private.empty()) {
1303
0
        stopOnError(
1304
0
            "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_thumbnail_private not "
1305
0
            "empty after placing thumbnails");
1306
0
    }
1307
1308
    // Place shared thumbnail objects
1309
9.10k
    for (auto const& og: lc_thumbnail_shared) {
1310
1.00k
        m->part9.push_back(getObject(og));
1311
1.00k
    }
1312
1313
    // Place outlines unless in first page
1314
9.10k
    if (!outlines_in_first_page) {
1315
8.40k
        pushOutlinesToPart(m->part9, lc_outlines, object_stream_data);
1316
8.40k
    }
1317
1318
    // Place all remaining objects
1319
18.8k
    for (auto const& og: lc_other) {
1320
18.8k
        m->part9.push_back(getObject(og));
1321
18.8k
    }
1322
1323
    // Make sure we got everything exactly once.
1324
1325
9.10k
    size_t num_placed =
1326
9.10k
        m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size();
1327
9.10k
    size_t num_wanted = m->object_to_obj_users.size();
1328
9.10k
    if (num_placed != num_wanted) {
1329
3
        stopOnError(
1330
3
            "INTERNAL ERROR: QPDF::calculateLinearizationData: wrong "
1331
3
            "number of objects placed (num_placed = " +
1332
3
            std::to_string(num_placed) + "; number of objects: " + std::to_string(num_wanted));
1333
3
    }
1334
1335
    // Calculate shared object hint table information including references to shared objects from
1336
    // page offset hint data.
1337
1338
    // The shared object hint table consists of all part 6 (whether shared or not) in order followed
1339
    // by all part 8 objects in order.  Add the objects to shared object data keeping a map of
1340
    // object number to index.  Then populate the shared object information for the pages.
1341
1342
    // Note that two objects never have the same object number, so we can map from object number
1343
    // only without regards to generation.
1344
9.10k
    std::map<int, int> obj_to_index;
1345
1346
9.10k
    m->c_shared_object_data.nshared_first_page = toI(m->part6.size());
1347
9.10k
    m->c_shared_object_data.nshared_total =
1348
9.10k
        m->c_shared_object_data.nshared_first_page + toI(m->part8.size());
1349
1350
9.10k
    std::vector<CHSharedObjectEntry>& shared = m->c_shared_object_data.entries;
1351
62.5k
    for (auto& oh: m->part6) {
1352
62.5k
        int obj = oh.getObjectID();
1353
62.5k
        obj_to_index[obj] = toI(shared.size());
1354
62.5k
        shared.emplace_back(obj);
1355
62.5k
    }
1356
9.10k
    QTC::TC("qpdf", "QPDF lin part 8 empty", m->part8.empty() ? 1 : 0);
1357
9.10k
    if (!m->part8.empty()) {
1358
99
        m->c_shared_object_data.first_shared_obj = m->part8.at(0).getObjectID();
1359
2.59k
        for (auto& oh: m->part8) {
1360
2.59k
            int obj = oh.getObjectID();
1361
2.59k
            obj_to_index[obj] = toI(shared.size());
1362
2.59k
            shared.emplace_back(obj);
1363
2.59k
        }
1364
99
    }
1365
9.10k
    if (static_cast<size_t>(m->c_shared_object_data.nshared_total) !=
1366
9.10k
        m->c_shared_object_data.entries.size()) {
1367
0
        stopOnError("shared object hint table has wrong number of entries");
1368
0
    }
1369
1370
    // Now compute the list of shared objects for each page after the first page.
1371
1372
14.3k
    for (size_t i = 1; i < npages; ++i) {
1373
5.19k
        CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i);
1374
5.19k
        ObjUser ou(ObjUser::ou_page, i);
1375
5.19k
        if (!m->obj_user_to_objects.contains(ou)) {
1376
0
            stopOnError("found unreferenced page while calculating linearization data");
1377
0
        }
1378
71.4k
        for (auto const& og: m->obj_user_to_objects[ou]) {
1379
71.4k
            if ((m->object_to_obj_users[og].size() > 1) && (obj_to_index.contains(og.getObj()))) {
1380
57.3k
                int idx = obj_to_index[og.getObj()];
1381
57.3k
                ++pe.nshared_objects;
1382
57.3k
                pe.shared_identifiers.push_back(idx);
1383
57.3k
            }
1384
71.4k
        }
1385
5.19k
    }
1386
9.10k
}
Unexecuted instantiation: void QPDF::calculateLinearizationData<std::__1::map<int, int, std::__1::less<int>, std::__1::allocator<std::__1::pair<int const, int> > > >(std::__1::map<int, int, std::__1::less<int>, std::__1::allocator<std::__1::pair<int const, int> > > const&)
void QPDF::calculateLinearizationData<QPDFWriter::ObjTable>(QPDFWriter::ObjTable const&)
Line
Count
Source
959
9.10k
{
960
    // This function calculates the ordering of objects, divides them into the appropriate parts,
961
    // and computes some values for the linearization parameter dictionary and hint tables.  The
962
    // file must be optimized (via calling optimize()) prior to calling this function.  Note that
963
    // actual offsets and lengths are not computed here, but anything related to object ordering is.
964
965
9.10k
    if (m->object_to_obj_users.empty()) {
966
        // Note that we can't call optimize here because we don't know whether it should be called
967
        // with or without allow changes.
968
0
        throw std::logic_error(
969
0
            "INTERNAL ERROR: QPDF::calculateLinearizationData called before optimize()");
970
0
    }
971
972
    // Separate objects into the categories sufficient for us to determine which part of the
973
    // linearized file should contain the object.  This categorization is useful for other purposes
974
    // as well.  Part numbers refer to version 1.4 of the PDF spec.
975
976
    // Parts 1, 3, 5, 10, and 11 don't contain any objects from the original file (except the
977
    // trailer dictionary in part 11).
978
979
    // Part 4 is the document catalog (root) and the following root keys: /ViewerPreferences,
980
    // /PageMode, /Threads, /OpenAction, /AcroForm, /Encrypt.  Note that Thread information
981
    // dictionaries are supposed to appear in part 9, but we are disregarding that recommendation
982
    // for now.
983
984
    // Part 6 is the first page section.  It includes all remaining objects referenced by the first
985
    // page including shared objects but not including thumbnails.  Additionally, if /PageMode is
986
    // /Outlines, then information from /Outlines also appears here.
987
988
    // Part 7 contains remaining objects private to pages other than the first page.
989
990
    // Part 8 contains all remaining shared objects except those that are shared only within
991
    // thumbnails.
992
993
    // Part 9 contains all remaining objects.
994
995
    // We sort objects into the following categories:
996
997
    //   * open_document: part 4
998
999
    //   * first_page_private: part 6
1000
1001
    //   * first_page_shared: part 6
1002
1003
    //   * other_page_private: part 7
1004
1005
    //   * other_page_shared: part 8
1006
1007
    //   * thumbnail_private: part 9
1008
1009
    //   * thumbnail_shared: part 9
1010
1011
    //   * other: part 9
1012
1013
    //   * outlines: part 6 or 9
1014
1015
9.10k
    m->part4.clear();
1016
9.10k
    m->part6.clear();
1017
9.10k
    m->part7.clear();
1018
9.10k
    m->part8.clear();
1019
9.10k
    m->part9.clear();
1020
9.10k
    m->c_linp = LinParameters();
1021
9.10k
    m->c_page_offset_data = CHPageOffset();
1022
9.10k
    m->c_shared_object_data = CHSharedObject();
1023
9.10k
    m->c_outline_data = HGeneric();
1024
1025
9.10k
    QPDFObjectHandle root = getRoot();
1026
9.10k
    bool outlines_in_first_page = false;
1027
9.10k
    QPDFObjectHandle pagemode = root.getKey("/PageMode");
1028
9.10k
    QTC::TC("qpdf", "QPDF categorize pagemode present", pagemode.isName() ? 1 : 0);
1029
9.10k
    if (pagemode.isName()) {
1030
331
        if (pagemode.getName() == "/UseOutlines") {
1031
184
            if (root.hasKey("/Outlines")) {
1032
75
                outlines_in_first_page = true;
1033
109
            } else {
1034
109
                QTC::TC("qpdf", "QPDF UseOutlines but no Outlines");
1035
109
            }
1036
184
        }
1037
331
        QTC::TC("qpdf", "QPDF categorize pagemode outlines", outlines_in_first_page ? 1 : 0);
1038
331
    }
1039
1040
9.10k
    std::set<std::string> open_document_keys;
1041
9.10k
    open_document_keys.insert("/ViewerPreferences");
1042
9.10k
    open_document_keys.insert("/PageMode");
1043
9.10k
    open_document_keys.insert("/Threads");
1044
9.10k
    open_document_keys.insert("/OpenAction");
1045
9.10k
    open_document_keys.insert("/AcroForm");
1046
1047
9.10k
    std::set<QPDFObjGen> lc_open_document;
1048
9.10k
    std::set<QPDFObjGen> lc_first_page_private;
1049
9.10k
    std::set<QPDFObjGen> lc_first_page_shared;
1050
9.10k
    std::set<QPDFObjGen> lc_other_page_private;
1051
9.10k
    std::set<QPDFObjGen> lc_other_page_shared;
1052
9.10k
    std::set<QPDFObjGen> lc_thumbnail_private;
1053
9.10k
    std::set<QPDFObjGen> lc_thumbnail_shared;
1054
9.10k
    std::set<QPDFObjGen> lc_other;
1055
9.10k
    std::set<QPDFObjGen> lc_outlines;
1056
9.10k
    std::set<QPDFObjGen> lc_root;
1057
1058
145k
    for (auto& oiter: m->object_to_obj_users) {
1059
145k
        QPDFObjGen const& og = oiter.first;
1060
145k
        std::set<ObjUser>& ous = oiter.second;
1061
1062
145k
        bool in_open_document = false;
1063
145k
        bool in_first_page = false;
1064
145k
        int other_pages = 0;
1065
145k
        int thumbs = 0;
1066
145k
        int others = 0;
1067
145k
        bool in_outlines = false;
1068
145k
        bool is_root = false;
1069
1070
301k
        for (auto const& ou: ous) {
1071
301k
            switch (ou.ou_type) {
1072
45.4k
            case ObjUser::ou_trailer_key:
1073
45.4k
                if (ou.key == "/Encrypt") {
1074
732
                    in_open_document = true;
1075
44.7k
                } else {
1076
44.7k
                    ++others;
1077
44.7k
                }
1078
45.4k
                break;
1079
1080
6.36k
            case ObjUser::ou_thumb:
1081
6.36k
                ++thumbs;
1082
6.36k
                break;
1083
1084
97.2k
            case ObjUser::ou_root_key:
1085
97.2k
                if (open_document_keys.contains(ou.key)) {
1086
16.6k
                    in_open_document = true;
1087
80.5k
                } else if (ou.key == "/Outlines") {
1088
3.88k
                    in_outlines = true;
1089
76.6k
                } else {
1090
76.6k
                    ++others;
1091
76.6k
                }
1092
97.2k
                break;
1093
1094
143k
            case ObjUser::ou_page:
1095
143k
                if (ou.pageno == 0) {
1096
69.7k
                    in_first_page = true;
1097
73.3k
                } else {
1098
73.3k
                    ++other_pages;
1099
73.3k
                }
1100
143k
                break;
1101
1102
9.10k
            case ObjUser::ou_root:
1103
9.10k
                is_root = true;
1104
9.10k
                break;
1105
301k
            }
1106
301k
        }
1107
1108
145k
        if (is_root) {
1109
9.10k
            lc_root.insert(og);
1110
135k
        } else if (in_outlines) {
1111
3.86k
            lc_outlines.insert(og);
1112
132k
        } else if (in_open_document) {
1113
17.3k
            lc_open_document.insert(og);
1114
114k
        } else if ((in_first_page) && (others == 0) && (other_pages == 0) && (thumbs == 0)) {
1115
50.8k
            lc_first_page_private.insert(og);
1116
63.8k
        } else if (in_first_page) {
1117
14.7k
            lc_first_page_shared.insert(og);
1118
49.1k
        } else if ((other_pages == 1) && (others == 0) && (thumbs == 0)) {
1119
12.4k
            lc_other_page_private.insert(og);
1120
36.6k
        } else if (other_pages > 1) {
1121
2.62k
            lc_other_page_shared.insert(og);
1122
34.0k
        } else if ((thumbs == 1) && (others == 0)) {
1123
2.17k
            lc_thumbnail_private.insert(og);
1124
31.8k
        } else if (thumbs > 1) {
1125
1.21k
            lc_thumbnail_shared.insert(og);
1126
30.6k
        } else {
1127
30.6k
            lc_other.insert(og);
1128
30.6k
        }
1129
145k
    }
1130
1131
    // Generate ordering for objects in the output file.  Sometimes we just dump right from a set
1132
    // into a vector.  Rather than optimizing this by going straight into the vector, we'll leave
1133
    // these phases separate for now.  That way, this section can be concerned only with ordering,
1134
    // and the above section can be considered only with categorization.  Note that sets of
1135
    // QPDFObjGens are sorted by QPDFObjGen.  In a linearized file, objects appear in sequence with
1136
    // the possible exception of hints tables which we won't see here anyway.  That means that
1137
    // running calculateLinearizationData() on a linearized file should give results identical to
1138
    // the original file ordering.
1139
1140
    // We seem to traverse the page tree a lot in this code, but we can address this for a future
1141
    // code optimization if necessary. Premature optimization is the root of all evil.
1142
9.10k
    std::vector<QPDFObjectHandle> pages;
1143
9.10k
    { // local scope
1144
        // Map all page objects to the containing object stream.  This should be a no-op in a
1145
        // properly linearized file.
1146
14.5k
        for (auto oh: getAllPages()) {
1147
14.5k
            pages.push_back(getUncompressedObject(oh, object_stream_data));
1148
14.5k
        }
1149
9.10k
    }
1150
9.10k
    size_t npages = pages.size();
1151
1152
    // We will be initializing some values of the computed hint tables.  Specifically, we can
1153
    // initialize any items that deal with object numbers or counts but not any items that deal with
1154
    // lengths or offsets.  The code that writes linearized files will have to fill in these values
1155
    // during the first pass.  The validation code can compute them relatively easily given the rest
1156
    // of the information.
1157
1158
    // npages is the size of the existing pages vector, which has been created by traversing the
1159
    // pages tree, and as such is a reasonable size.
1160
9.10k
    m->c_linp.npages = npages;
1161
9.10k
    m->c_page_offset_data.entries = std::vector<CHPageOffsetEntry>(npages);
1162
1163
    // Part 4: open document objects.  We don't care about the order.
1164
1165
9.10k
    if (lc_root.size() != 1) {
1166
0
        stopOnError("found other than one root while calculating linearization data");
1167
0
    }
1168
9.10k
    m->part4.push_back(getObject(*(lc_root.begin())));
1169
17.3k
    for (auto const& og: lc_open_document) {
1170
17.3k
        m->part4.push_back(getObject(og));
1171
17.3k
    }
1172
1173
    // Part 6: first page objects.  Note: implementation note 124 states that Acrobat always treats
1174
    // page 0 as the first page for linearization regardless of /OpenAction.  pdlin doesn't provide
1175
    // any option to set this and also disregards /OpenAction.  We will do the same.
1176
1177
    // First, place the actual first page object itself.
1178
9.10k
    if (pages.empty()) {
1179
59
        stopOnError("no pages found while calculating linearization data");
1180
59
    }
1181
9.10k
    QPDFObjGen first_page_og(pages.at(0).getObjGen());
1182
9.10k
    if (!lc_first_page_private.contains(first_page_og)) {
1183
495
        stopOnError(
1184
495
            "INTERNAL ERROR: QPDF::calculateLinearizationData: first page "
1185
495
            "object not in lc_first_page_private");
1186
495
    }
1187
9.10k
    lc_first_page_private.erase(first_page_og);
1188
9.10k
    m->c_linp.first_page_object = pages.at(0).getObjectID();
1189
9.10k
    m->part6.push_back(pages.at(0));
1190
1191
    // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard
1192
    // it except to the extent that it groups private and shared objects contiguously for the sake
1193
    // of hint tables.
1194
1195
42.2k
    for (auto const& og: lc_first_page_private) {
1196
42.2k
        m->part6.push_back(getObject(og));
1197
42.2k
    }
1198
1199
9.27k
    for (auto const& og: lc_first_page_shared) {
1200
9.27k
        m->part6.push_back(getObject(og));
1201
9.27k
    }
1202
1203
    // Place the outline dictionary if it goes in the first page section.
1204
9.10k
    if (outlines_in_first_page) {
1205
75
        pushOutlinesToPart(m->part6, lc_outlines, object_stream_data);
1206
75
    }
1207
1208
    // Fill in page offset hint table information for the first page. The PDF spec says that
1209
    // nshared_objects should be zero for the first page.  pdlin does not appear to obey this, but
1210
    // it fills in garbage values for all the shared object identifiers on the first page.
1211
1212
9.10k
    m->c_page_offset_data.entries.at(0).nobjects = toI(m->part6.size());
1213
1214
    // Part 7: other pages' private objects
1215
1216
    // For each page in order:
1217
14.4k
    for (size_t i = 1; i < npages; ++i) {
1218
        // Place this page's page object
1219
1220
5.33k
        QPDFObjGen page_og(pages.at(i).getObjGen());
1221
5.33k
        if (!lc_other_page_private.contains(page_og)) {
1222
58
            stopOnError(
1223
58
                "INTERNAL ERROR: QPDF::calculateLinearizationData: page object for page " +
1224
58
                std::to_string(i) + " not in lc_other_page_private");
1225
58
        }
1226
5.33k
        lc_other_page_private.erase(page_og);
1227
5.33k
        m->part7.push_back(pages.at(i));
1228
1229
        // Place all non-shared objects referenced by this page, updating the page object count for
1230
        // the hint table.
1231
1232
5.33k
        m->c_page_offset_data.entries.at(i).nobjects = 1;
1233
1234
5.33k
        ObjUser ou(ObjUser::ou_page, i);
1235
5.33k
        if (!m->obj_user_to_objects.contains(ou)) {
1236
0
            stopOnError("found unreferenced page while calculating linearization data");
1237
0
        }
1238
71.8k
        for (auto const& og: m->obj_user_to_objects[ou]) {
1239
71.8k
            if (lc_other_page_private.contains(og)) {
1240
6.97k
                lc_other_page_private.erase(og);
1241
6.97k
                m->part7.push_back(getObject(og));
1242
6.97k
                ++m->c_page_offset_data.entries.at(i).nobjects;
1243
6.97k
            }
1244
71.8k
        }
1245
5.33k
    }
1246
    // That should have covered all part7 objects.
1247
9.10k
    if (!lc_other_page_private.empty()) {
1248
0
        stopOnError(
1249
0
            "INTERNAL ERROR: QPDF::calculateLinearizationData:"
1250
0
            " lc_other_page_private is not empty after generation of part7");
1251
0
    }
1252
1253
    // Part 8: other pages' shared objects
1254
1255
    // Order is unimportant.
1256
9.10k
    for (auto const& og: lc_other_page_shared) {
1257
2.59k
        m->part8.push_back(getObject(og));
1258
2.59k
    }
1259
1260
    // Part 9: other objects
1261
1262
    // The PDF specification makes recommendations on ordering here. We follow them only to a
1263
    // limited extent.  Specifically, we put the pages tree first, then private thumbnail objects in
1264
    // page order, then shared thumbnail objects, and then outlines (unless in part 6).  After that,
1265
    // we throw all remaining objects in arbitrary order.
1266
1267
    // Place the pages tree.
1268
9.10k
    std::set<QPDFObjGen> pages_ogs =
1269
9.10k
        m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")];
1270
9.10k
    if (pages_ogs.empty()) {
1271
21
        stopOnError("found empty pages tree while calculating linearization data");
1272
21
    }
1273
11.8k
    for (auto const& og: pages_ogs) {
1274
11.8k
        if (lc_other.contains(og)) {
1275
9.75k
            lc_other.erase(og);
1276
9.75k
            m->part9.push_back(getObject(og));
1277
9.75k
        }
1278
11.8k
    }
1279
1280
    // Place private thumbnail images in page order.  Slightly more information would be required if
1281
    // we were going to bother with thumbnail hint tables.
1282
22.8k
    for (size_t i = 0; i < npages; ++i) {
1283
13.7k
        QPDFObjectHandle thumb = pages.at(i).getKey("/Thumb");
1284
13.7k
        thumb = getUncompressedObject(thumb, object_stream_data);
1285
13.7k
        QPDFObjGen thumb_og(thumb.getObjGen());
1286
        // Output the thumbnail itself
1287
13.7k
        if (lc_thumbnail_private.erase(thumb_og) && !thumb.isNull()) {
1288
310
            m->part9.emplace_back(thumb);
1289
13.3k
        } else {
1290
            // No internal error this time...there's nothing to stop this object from having
1291
            // been referred to somewhere else outside of a page's /Thumb, and if it had been,
1292
            // there's nothing to prevent it from having been in some set other than
1293
            // lc_thumbnail_private.
1294
13.3k
        }
1295
13.7k
        std::set<QPDFObjGen>& ogs = m->obj_user_to_objects[ObjUser(ObjUser::ou_thumb, i)];
1296
13.7k
        for (auto const& og: ogs) {
1297
5.65k
            if (lc_thumbnail_private.erase(og)) {
1298
1.79k
                m->part9.emplace_back(getObject(og));
1299
1.79k
            }
1300
5.65k
        }
1301
13.7k
    }
1302
9.10k
    if (!lc_thumbnail_private.empty()) {
1303
0
        stopOnError(
1304
0
            "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_thumbnail_private not "
1305
0
            "empty after placing thumbnails");
1306
0
    }
1307
1308
    // Place shared thumbnail objects
1309
9.10k
    for (auto const& og: lc_thumbnail_shared) {
1310
1.00k
        m->part9.push_back(getObject(og));
1311
1.00k
    }
1312
1313
    // Place outlines unless in first page
1314
9.10k
    if (!outlines_in_first_page) {
1315
8.40k
        pushOutlinesToPart(m->part9, lc_outlines, object_stream_data);
1316
8.40k
    }
1317
1318
    // Place all remaining objects
1319
18.8k
    for (auto const& og: lc_other) {
1320
18.8k
        m->part9.push_back(getObject(og));
1321
18.8k
    }
1322
1323
    // Make sure we got everything exactly once.
1324
1325
9.10k
    size_t num_placed =
1326
9.10k
        m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size();
1327
9.10k
    size_t num_wanted = m->object_to_obj_users.size();
1328
9.10k
    if (num_placed != num_wanted) {
1329
3
        stopOnError(
1330
3
            "INTERNAL ERROR: QPDF::calculateLinearizationData: wrong "
1331
3
            "number of objects placed (num_placed = " +
1332
3
            std::to_string(num_placed) + "; number of objects: " + std::to_string(num_wanted));
1333
3
    }
1334
1335
    // Calculate shared object hint table information including references to shared objects from
1336
    // page offset hint data.
1337
1338
    // The shared object hint table consists of all part 6 (whether shared or not) in order followed
1339
    // by all part 8 objects in order.  Add the objects to shared object data keeping a map of
1340
    // object number to index.  Then populate the shared object information for the pages.
1341
1342
    // Note that two objects never have the same object number, so we can map from object number
1343
    // only without regards to generation.
1344
9.10k
    std::map<int, int> obj_to_index;
1345
1346
9.10k
    m->c_shared_object_data.nshared_first_page = toI(m->part6.size());
1347
9.10k
    m->c_shared_object_data.nshared_total =
1348
9.10k
        m->c_shared_object_data.nshared_first_page + toI(m->part8.size());
1349
1350
9.10k
    std::vector<CHSharedObjectEntry>& shared = m->c_shared_object_data.entries;
1351
62.5k
    for (auto& oh: m->part6) {
1352
62.5k
        int obj = oh.getObjectID();
1353
62.5k
        obj_to_index[obj] = toI(shared.size());
1354
62.5k
        shared.emplace_back(obj);
1355
62.5k
    }
1356
9.10k
    QTC::TC("qpdf", "QPDF lin part 8 empty", m->part8.empty() ? 1 : 0);
1357
9.10k
    if (!m->part8.empty()) {
1358
99
        m->c_shared_object_data.first_shared_obj = m->part8.at(0).getObjectID();
1359
2.59k
        for (auto& oh: m->part8) {
1360
2.59k
            int obj = oh.getObjectID();
1361
2.59k
            obj_to_index[obj] = toI(shared.size());
1362
2.59k
            shared.emplace_back(obj);
1363
2.59k
        }
1364
99
    }
1365
9.10k
    if (static_cast<size_t>(m->c_shared_object_data.nshared_total) !=
1366
9.10k
        m->c_shared_object_data.entries.size()) {
1367
0
        stopOnError("shared object hint table has wrong number of entries");
1368
0
    }
1369
1370
    // Now compute the list of shared objects for each page after the first page.
1371
1372
14.3k
    for (size_t i = 1; i < npages; ++i) {
1373
5.19k
        CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i);
1374
5.19k
        ObjUser ou(ObjUser::ou_page, i);
1375
5.19k
        if (!m->obj_user_to_objects.contains(ou)) {
1376
0
            stopOnError("found unreferenced page while calculating linearization data");
1377
0
        }
1378
71.4k
        for (auto const& og: m->obj_user_to_objects[ou]) {
1379
71.4k
            if ((m->object_to_obj_users[og].size() > 1) && (obj_to_index.contains(og.getObj()))) {
1380
57.3k
                int idx = obj_to_index[og.getObj()];
1381
57.3k
                ++pe.nshared_objects;
1382
57.3k
                pe.shared_identifiers.push_back(idx);
1383
57.3k
            }
1384
71.4k
        }
1385
5.19k
    }
1386
9.10k
}
1387
1388
template <typename T>
1389
void
1390
QPDF::pushOutlinesToPart(
1391
    std::vector<QPDFObjectHandle>& part,
1392
    std::set<QPDFObjGen>& lc_outlines,
1393
    T const& object_stream_data)
1394
8.47k
{
1395
8.47k
    QPDFObjectHandle root = getRoot();
1396
8.47k
    QPDFObjectHandle outlines = root.getKey("/Outlines");
1397
8.47k
    if (outlines.isNull()) {
1398
8.17k
        return;
1399
8.17k
    }
1400
300
    outlines = getUncompressedObject(outlines, object_stream_data);
1401
300
    QPDFObjGen outlines_og(outlines.getObjGen());
1402
300
    QTC::TC(
1403
300
        "qpdf",
1404
300
        "QPDF lin outlines in part",
1405
300
        &part == &m->part6         ? 0
1406
300
            : (&part == &m->part9) ? 1
1407
225
                                   : 9999); // can't happen
1408
300
    if (lc_outlines.erase(outlines_og)) {
1409
        // Make sure outlines is in lc_outlines in case the file is damaged. in which case it may be
1410
        // included in an earlier part.
1411
268
        part.push_back(outlines);
1412
268
        m->c_outline_data.first_object = outlines_og.getObj();
1413
268
        m->c_outline_data.nobjects = 1;
1414
268
    }
1415
3.46k
    for (auto const& og: lc_outlines) {
1416
3.46k
        if (!m->c_outline_data.first_object) {
1417
6
            m->c_outline_data.first_object = og.getObj();
1418
6
        }
1419
3.46k
        part.push_back(getObject(og));
1420
3.46k
        ++m->c_outline_data.nobjects;
1421
3.46k
    }
1422
300
}
Unexecuted instantiation: void QPDF::pushOutlinesToPart<std::__1::map<int, int, std::__1::less<int>, std::__1::allocator<std::__1::pair<int const, int> > > >(std::__1::vector<QPDFObjectHandle, std::__1::allocator<QPDFObjectHandle> >&, std::__1::set<QPDFObjGen, std::__1::less<QPDFObjGen>, std::__1::allocator<QPDFObjGen> >&, std::__1::map<int, int, std::__1::less<int>, std::__1::allocator<std::__1::pair<int const, int> > > const&)
void QPDF::pushOutlinesToPart<QPDFWriter::ObjTable>(std::__1::vector<QPDFObjectHandle, std::__1::allocator<QPDFObjectHandle> >&, std::__1::set<QPDFObjGen, std::__1::less<QPDFObjGen>, std::__1::allocator<QPDFObjGen> >&, QPDFWriter::ObjTable const&)
Line
Count
Source
1394
8.47k
{
1395
8.47k
    QPDFObjectHandle root = getRoot();
1396
8.47k
    QPDFObjectHandle outlines = root.getKey("/Outlines");
1397
8.47k
    if (outlines.isNull()) {
1398
8.17k
        return;
1399
8.17k
    }
1400
300
    outlines = getUncompressedObject(outlines, object_stream_data);
1401
300
    QPDFObjGen outlines_og(outlines.getObjGen());
1402
300
    QTC::TC(
1403
300
        "qpdf",
1404
300
        "QPDF lin outlines in part",
1405
300
        &part == &m->part6         ? 0
1406
300
            : (&part == &m->part9) ? 1
1407
225
                                   : 9999); // can't happen
1408
300
    if (lc_outlines.erase(outlines_og)) {
1409
        // Make sure outlines is in lc_outlines in case the file is damaged. in which case it may be
1410
        // included in an earlier part.
1411
268
        part.push_back(outlines);
1412
268
        m->c_outline_data.first_object = outlines_og.getObj();
1413
268
        m->c_outline_data.nobjects = 1;
1414
268
    }
1415
3.46k
    for (auto const& og: lc_outlines) {
1416
3.46k
        if (!m->c_outline_data.first_object) {
1417
6
            m->c_outline_data.first_object = og.getObj();
1418
6
        }
1419
3.46k
        part.push_back(getObject(og));
1420
3.46k
        ++m->c_outline_data.nobjects;
1421
3.46k
    }
1422
300
}
1423
1424
void
1425
QPDF::getLinearizedParts(
1426
    QPDFWriter::ObjTable const& obj,
1427
    std::vector<QPDFObjectHandle>& part4,
1428
    std::vector<QPDFObjectHandle>& part6,
1429
    std::vector<QPDFObjectHandle>& part7,
1430
    std::vector<QPDFObjectHandle>& part8,
1431
    std::vector<QPDFObjectHandle>& part9)
1432
9.10k
{
1433
9.10k
    calculateLinearizationData(obj);
1434
9.10k
    part4 = m->part4;
1435
9.10k
    part6 = m->part6;
1436
9.10k
    part7 = m->part7;
1437
9.10k
    part8 = m->part8;
1438
9.10k
    part9 = m->part9;
1439
9.10k
}
1440
1441
static inline int
1442
nbits(int val)
1443
126k
{
1444
126k
    return (val == 0 ? 0 : (1 + nbits(val >> 1)));
1445
126k
}
1446
1447
int
1448
QPDF::outputLengthNextN(
1449
    int in_object, int n, QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1450
76.4k
{
1451
    // Figure out the length of a series of n consecutive objects in the output file starting with
1452
    // whatever object in_object from the input file mapped to.
1453
1454
76.4k
    int first = obj[in_object].renumber;
1455
76.4k
    int last = first + n;
1456
76.4k
    if (first <= 0) {
1457
0
        stopOnError("found object that is not renumbered while writing linearization data");
1458
0
    }
1459
76.4k
    qpdf_offset_t length = 0;
1460
208k
    for (int i = first; i < last; ++i) {
1461
131k
        auto l = new_obj[i].length;
1462
131k
        if (l == 0) {
1463
0
            stopOnError("found item with unknown length while writing linearization data");
1464
0
        }
1465
131k
        length += l;
1466
131k
    }
1467
76.4k
    return toI(length);
1468
76.4k
}
1469
1470
void
1471
QPDF::calculateHPageOffset(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1472
7.61k
{
1473
    // Page Offset Hint Table
1474
1475
    // We are purposely leaving some values set to their initial zero values.
1476
1477
7.61k
    std::vector<QPDFObjectHandle> const& pages = getAllPages();
1478
7.61k
    size_t npages = pages.size();
1479
7.61k
    CHPageOffset& cph = m->c_page_offset_data;
1480
7.61k
    std::vector<CHPageOffsetEntry>& cphe = cph.entries;
1481
1482
    // Calculate minimum and maximum values for number of objects per page and page length.
1483
1484
7.61k
    int min_nobjects = std::numeric_limits<int>::max();
1485
7.61k
    int max_nobjects = 0;
1486
7.61k
    int min_length = std::numeric_limits<int>::max();
1487
7.61k
    int max_length = 0;
1488
7.61k
    int max_shared = 0;
1489
1490
7.61k
    HPageOffset& ph = m->page_offset_hints;
1491
7.61k
    std::vector<HPageOffsetEntry>& phe = ph.entries;
1492
    // npages is the size of the existing pages array.
1493
7.61k
    phe = std::vector<HPageOffsetEntry>(npages);
1494
1495
7.61k
    size_t i = 0;
1496
12.6k
    for (auto& phe_i: phe) {
1497
        // Calculate values for each page, assigning full values to the delta items.  They will be
1498
        // adjusted later.
1499
1500
        // Repeat calculations for page 0 so we can assign to phe[i] without duplicating those
1501
        // assignments.
1502
1503
12.6k
        int nobjects = cphe.at(i).nobjects;
1504
12.6k
        int length = outputLengthNextN(pages.at(i).getObjectID(), nobjects, new_obj, obj);
1505
12.6k
        int nshared = cphe.at(i).nshared_objects;
1506
1507
12.6k
        min_nobjects = std::min(min_nobjects, nobjects);
1508
12.6k
        max_nobjects = std::max(max_nobjects, nobjects);
1509
12.6k
        min_length = std::min(min_length, length);
1510
12.6k
        max_length = std::max(max_length, length);
1511
12.6k
        max_shared = std::max(max_shared, nshared);
1512
1513
12.6k
        phe_i.delta_nobjects = nobjects;
1514
12.6k
        phe_i.delta_page_length = length;
1515
12.6k
        phe_i.nshared_objects = nshared;
1516
12.6k
        ++i;
1517
12.6k
    }
1518
1519
7.61k
    ph.min_nobjects = min_nobjects;
1520
7.61k
    ph.first_page_offset = new_obj[obj[pages.at(0)].renumber].xref.getOffset();
1521
7.61k
    ph.nbits_delta_nobjects = nbits(max_nobjects - min_nobjects);
1522
7.61k
    ph.min_page_length = min_length;
1523
7.61k
    ph.nbits_delta_page_length = nbits(max_length - min_length);
1524
7.61k
    ph.nbits_nshared_objects = nbits(max_shared);
1525
7.61k
    ph.nbits_shared_identifier = nbits(m->c_shared_object_data.nshared_total);
1526
7.61k
    ph.shared_denominator = 4; // doesn't matter
1527
1528
    // It isn't clear how to compute content offset and content length.  Since we are not
1529
    // interleaving page objects with the content stream, we'll use the same values for content
1530
    // length as page length.  We will use 0 as content offset because this is what Adobe does
1531
    // (implementation note 127) and pdlin as well.
1532
7.61k
    ph.nbits_delta_content_length = ph.nbits_delta_page_length;
1533
7.61k
    ph.min_content_length = ph.min_page_length;
1534
1535
7.61k
    i = 0;
1536
12.6k
    for (auto& phe_i: phe) {
1537
        // Adjust delta entries
1538
12.6k
        if (phe_i.delta_nobjects < min_nobjects || phe_i.delta_page_length < min_length) {
1539
0
            stopOnError(
1540
0
                "found too small delta nobjects or delta page length while writing "
1541
0
                "linearization data");
1542
0
        }
1543
12.6k
        phe_i.delta_nobjects -= min_nobjects;
1544
12.6k
        phe_i.delta_page_length -= min_length;
1545
12.6k
        phe_i.delta_content_length = phe_i.delta_page_length;
1546
1547
12.6k
        auto& si = cphe.at(i).shared_identifiers;
1548
12.6k
        phe_i.shared_identifiers.insert(phe_i.shared_identifiers.end(), si.begin(), si.end());
1549
12.6k
        phe_i.shared_numerators.insert(phe_i.shared_numerators.end(), si.size(), 0);
1550
12.6k
        ++i;
1551
12.6k
    }
1552
7.61k
}
1553
1554
void
1555
QPDF::calculateHSharedObject(
1556
    QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1557
7.61k
{
1558
7.61k
    CHSharedObject& cso = m->c_shared_object_data;
1559
7.61k
    std::vector<CHSharedObjectEntry>& csoe = cso.entries;
1560
7.61k
    HSharedObject& so = m->shared_object_hints;
1561
7.61k
    std::vector<HSharedObjectEntry>& soe = so.entries;
1562
7.61k
    soe.clear();
1563
1564
7.61k
    int min_length = outputLengthNextN(csoe.at(0).object, 1, new_obj, obj);
1565
7.61k
    int max_length = min_length;
1566
1567
63.5k
    for (size_t i = 0; i < toS(cso.nshared_total); ++i) {
1568
        // Assign absolute numbers to deltas; adjust later
1569
55.9k
        int length = outputLengthNextN(csoe.at(i).object, 1, new_obj, obj);
1570
55.9k
        min_length = std::min(min_length, length);
1571
55.9k
        max_length = std::max(max_length, length);
1572
55.9k
        soe.emplace_back();
1573
55.9k
        soe.at(i).delta_group_length = length;
1574
55.9k
    }
1575
7.61k
    if (soe.size() != toS(cso.nshared_total)) {
1576
0
        stopOnError("soe has wrong size after initialization");
1577
0
    }
1578
1579
7.61k
    so.nshared_total = cso.nshared_total;
1580
7.61k
    so.nshared_first_page = cso.nshared_first_page;
1581
7.61k
    if (so.nshared_total > so.nshared_first_page) {
1582
87
        so.first_shared_obj = obj[cso.first_shared_obj].renumber;
1583
87
        so.min_group_length = min_length;
1584
87
        so.first_shared_offset = new_obj[so.first_shared_obj].xref.getOffset();
1585
87
    }
1586
7.61k
    so.min_group_length = min_length;
1587
7.61k
    so.nbits_delta_group_length = nbits(max_length - min_length);
1588
1589
63.5k
    for (size_t i = 0; i < toS(cso.nshared_total); ++i) {
1590
        // Adjust deltas
1591
55.9k
        if (soe.at(i).delta_group_length < min_length) {
1592
0
            stopOnError("found too small group length while writing linearization data");
1593
0
        }
1594
55.9k
        soe.at(i).delta_group_length -= min_length;
1595
55.9k
    }
1596
7.61k
}
1597
1598
void
1599
QPDF::calculateHOutline(QPDFWriter::NewObjTable const& new_obj, QPDFWriter::ObjTable const& obj)
1600
7.61k
{
1601
7.61k
    HGeneric& cho = m->c_outline_data;
1602
1603
7.61k
    if (cho.nobjects == 0) {
1604
7.34k
        return;
1605
7.34k
    }
1606
1607
263
    HGeneric& ho = m->outline_hints;
1608
1609
263
    ho.first_object = obj[cho.first_object].renumber;
1610
263
    ho.first_object_offset = new_obj[ho.first_object].xref.getOffset();
1611
263
    ho.nobjects = cho.nobjects;
1612
263
    ho.group_length = outputLengthNextN(cho.first_object, ho.nobjects, new_obj, obj);
1613
263
}
1614
1615
template <class T, class int_type>
1616
static void
1617
write_vector_int(BitWriter& w, int nitems, std::vector<T>& vec, int bits, int_type T::* field)
1618
60.8k
{
1619
    // nitems times, write bits bits from the given field of the ith vector to the given bit writer.
1620
1621
291k
    for (size_t i = 0; i < QIntC::to_size(nitems); ++i) {
1622
231k
        w.writeBits(QIntC::to_ulonglong(vec.at(i).*field), QIntC::to_size(bits));
1623
231k
    }
1624
    // The PDF spec says that each hint table starts at a byte boundary.  Each "row" actually must
1625
    // start on a byte boundary.
1626
60.8k
    w.flush();
1627
60.8k
}
QPDF_linearization.cc:void write_vector_int<QPDF::HPageOffsetEntry, int>(BitWriter&, int, std::__1::vector<QPDF::HPageOffsetEntry, std::__1::allocator<QPDF::HPageOffsetEntry> >&, int, int QPDF::HPageOffsetEntry::*)
Line
Count
Source
1618
15.2k
{
1619
    // nitems times, write bits bits from the given field of the ith vector to the given bit writer.
1620
1621
40.5k
    for (size_t i = 0; i < QIntC::to_size(nitems); ++i) {
1622
25.2k
        w.writeBits(QIntC::to_ulonglong(vec.at(i).*field), QIntC::to_size(bits));
1623
25.2k
    }
1624
    // The PDF spec says that each hint table starts at a byte boundary.  Each "row" actually must
1625
    // start on a byte boundary.
1626
15.2k
    w.flush();
1627
15.2k
}
QPDF_linearization.cc:void write_vector_int<QPDF::HPageOffsetEntry, long long>(BitWriter&, int, std::__1::vector<QPDF::HPageOffsetEntry, std::__1::allocator<QPDF::HPageOffsetEntry> >&, int, long long QPDF::HPageOffsetEntry::*)
Line
Count
Source
1618
22.8k
{
1619
    // nitems times, write bits bits from the given field of the ith vector to the given bit writer.
1620
1621
60.7k
    for (size_t i = 0; i < QIntC::to_size(nitems); ++i) {
1622
37.9k
        w.writeBits(QIntC::to_ulonglong(vec.at(i).*field), QIntC::to_size(bits));
1623
37.9k
    }
1624
    // The PDF spec says that each hint table starts at a byte boundary.  Each "row" actually must
1625
    // start on a byte boundary.
1626
22.8k
    w.flush();
1627
22.8k
}
QPDF_linearization.cc:void write_vector_int<QPDF::HSharedObjectEntry, int>(BitWriter&, int, std::__1::vector<QPDF::HSharedObjectEntry, std::__1::allocator<QPDF::HSharedObjectEntry> >&, int, int QPDF::HSharedObjectEntry::*)
Line
Count
Source
1618
22.8k
{
1619
    // nitems times, write bits bits from the given field of the ith vector to the given bit writer.
1620
1621
190k
    for (size_t i = 0; i < QIntC::to_size(nitems); ++i) {
1622
167k
        w.writeBits(QIntC::to_ulonglong(vec.at(i).*field), QIntC::to_size(bits));
1623
167k
    }
1624
    // The PDF spec says that each hint table starts at a byte boundary.  Each "row" actually must
1625
    // start on a byte boundary.
1626
22.8k
    w.flush();
1627
22.8k
}
1628
1629
template <class T>
1630
static void
1631
write_vector_vector(
1632
    BitWriter& w,
1633
    int nitems1,
1634
    std::vector<T>& vec1,
1635
    int T::* nitems2,
1636
    int bits,
1637
    std::vector<int> T::* vec2)
1638
15.2k
{
1639
    // nitems1 times, write nitems2 (from the ith element of vec1) items from the vec2 vector field
1640
    // of the ith item of vec1.
1641
40.5k
    for (size_t i1 = 0; i1 < QIntC::to_size(nitems1); ++i1) {
1642
134k
        for (size_t i2 = 0; i2 < QIntC::to_size(vec1.at(i1).*nitems2); ++i2) {
1643
109k
            w.writeBits(QIntC::to_ulonglong((vec1.at(i1).*vec2).at(i2)), QIntC::to_size(bits));
1644
109k
        }
1645
25.2k
    }
1646
15.2k
    w.flush();
1647
15.2k
}
1648
1649
void
1650
QPDF::writeHPageOffset(BitWriter& w)
1651
7.61k
{
1652
7.61k
    HPageOffset& t = m->page_offset_hints;
1653
1654
7.61k
    w.writeBitsInt(t.min_nobjects, 32);               // 1
1655
7.61k
    w.writeBits(toULL(t.first_page_offset), 32);      // 2
1656
7.61k
    w.writeBitsInt(t.nbits_delta_nobjects, 16);       // 3
1657
7.61k
    w.writeBitsInt(t.min_page_length, 32);            // 4
1658
7.61k
    w.writeBitsInt(t.nbits_delta_page_length, 16);    // 5
1659
7.61k
    w.writeBits(toULL(t.min_content_offset), 32);     // 6
1660
7.61k
    w.writeBitsInt(t.nbits_delta_content_offset, 16); // 7
1661
7.61k
    w.writeBitsInt(t.min_content_length, 32);         // 8
1662
7.61k
    w.writeBitsInt(t.nbits_delta_content_length, 16); // 9
1663
7.61k
    w.writeBitsInt(t.nbits_nshared_objects, 16);      // 10
1664
7.61k
    w.writeBitsInt(t.nbits_shared_identifier, 16);    // 11
1665
7.61k
    w.writeBitsInt(t.nbits_shared_numerator, 16);     // 12
1666
7.61k
    w.writeBitsInt(t.shared_denominator, 16);         // 13
1667
1668
7.61k
    int nitems = toI(getAllPages().size());
1669
7.61k
    std::vector<HPageOffsetEntry>& entries = t.entries;
1670
1671
7.61k
    write_vector_int(w, nitems, entries, t.nbits_delta_nobjects, &HPageOffsetEntry::delta_nobjects);
1672
7.61k
    write_vector_int(
1673
7.61k
        w, nitems, entries, t.nbits_delta_page_length, &HPageOffsetEntry::delta_page_length);
1674
7.61k
    write_vector_int(
1675
7.61k
        w, nitems, entries, t.nbits_nshared_objects, &HPageOffsetEntry::nshared_objects);
1676
7.61k
    write_vector_vector(
1677
7.61k
        w,
1678
7.61k
        nitems,
1679
7.61k
        entries,
1680
7.61k
        &HPageOffsetEntry::nshared_objects,
1681
7.61k
        t.nbits_shared_identifier,
1682
7.61k
        &HPageOffsetEntry::shared_identifiers);
1683
7.61k
    write_vector_vector(
1684
7.61k
        w,
1685
7.61k
        nitems,
1686
7.61k
        entries,
1687
7.61k
        &HPageOffsetEntry::nshared_objects,
1688
7.61k
        t.nbits_shared_numerator,
1689
7.61k
        &HPageOffsetEntry::shared_numerators);
1690
7.61k
    write_vector_int(
1691
7.61k
        w, nitems, entries, t.nbits_delta_content_offset, &HPageOffsetEntry::delta_content_offset);
1692
7.61k
    write_vector_int(
1693
7.61k
        w, nitems, entries, t.nbits_delta_content_length, &HPageOffsetEntry::delta_content_length);
1694
7.61k
}
1695
1696
void
1697
QPDF::writeHSharedObject(BitWriter& w)
1698
7.61k
{
1699
7.61k
    HSharedObject& t = m->shared_object_hints;
1700
1701
7.61k
    w.writeBitsInt(t.first_shared_obj, 32);         // 1
1702
7.61k
    w.writeBits(toULL(t.first_shared_offset), 32);  // 2
1703
7.61k
    w.writeBitsInt(t.nshared_first_page, 32);       // 3
1704
7.61k
    w.writeBitsInt(t.nshared_total, 32);            // 4
1705
7.61k
    w.writeBitsInt(t.nbits_nobjects, 16);           // 5
1706
7.61k
    w.writeBitsInt(t.min_group_length, 32);         // 6
1707
7.61k
    w.writeBitsInt(t.nbits_delta_group_length, 16); // 7
1708
1709
7.61k
    QTC::TC(
1710
7.61k
        "qpdf",
1711
7.61k
        "QPDF lin write nshared_total > nshared_first_page",
1712
7.61k
        (t.nshared_total > t.nshared_first_page) ? 1 : 0);
1713
1714
7.61k
    int nitems = t.nshared_total;
1715
7.61k
    std::vector<HSharedObjectEntry>& entries = t.entries;
1716
1717
7.61k
    write_vector_int(
1718
7.61k
        w, nitems, entries, t.nbits_delta_group_length, &HSharedObjectEntry::delta_group_length);
1719
7.61k
    write_vector_int(w, nitems, entries, 1, &HSharedObjectEntry::signature_present);
1720
63.5k
    for (size_t i = 0; i < toS(nitems); ++i) {
1721
        // If signature were present, we'd have to write a 128-bit hash.
1722
55.9k
        if (entries.at(i).signature_present != 0) {
1723
0
            stopOnError("found unexpected signature present while writing linearization data");
1724
0
        }
1725
55.9k
    }
1726
7.61k
    write_vector_int(w, nitems, entries, t.nbits_nobjects, &HSharedObjectEntry::nobjects_minus_one);
1727
7.61k
}
1728
1729
void
1730
QPDF::writeHGeneric(BitWriter& w, HGeneric& t)
1731
263
{
1732
263
    w.writeBitsInt(t.first_object, 32);            // 1
1733
263
    w.writeBits(toULL(t.first_object_offset), 32); // 2
1734
263
    w.writeBitsInt(t.nobjects, 32);                // 3
1735
263
    w.writeBitsInt(t.group_length, 32);            // 4
1736
263
}
1737
1738
void
1739
QPDF::generateHintStream(
1740
    QPDFWriter::NewObjTable const& new_obj,
1741
    QPDFWriter::ObjTable const& obj,
1742
    std::string& hint_buffer,
1743
    int& S,
1744
    int& O,
1745
    bool compressed)
1746
7.61k
{
1747
    // Populate actual hint table values
1748
7.61k
    calculateHPageOffset(new_obj, obj);
1749
7.61k
    calculateHSharedObject(new_obj, obj);
1750
7.61k
    calculateHOutline(new_obj, obj);
1751
1752
    // Write the hint stream itself into a compressed memory buffer. Write through a counter so we
1753
    // can get offsets.
1754
7.61k
    pl::Count c(0, hint_buffer);
1755
7.61k
    BitWriter w(&c);
1756
1757
7.61k
    writeHPageOffset(w);
1758
7.61k
    S = toI(c.getCount());
1759
7.61k
    writeHSharedObject(w);
1760
7.61k
    O = 0;
1761
7.61k
    if (m->outline_hints.nobjects > 0) {
1762
263
        O = toI(c.getCount());
1763
263
        writeHGeneric(w, m->outline_hints);
1764
263
    }
1765
7.61k
    if (compressed) {
1766
7.61k
        hint_buffer = pl::pipe<Pl_Flate>(hint_buffer, Pl_Flate::a_deflate);
1767
7.61k
    }
1768
7.61k
}