Coverage Report

Created: 2026-08-31 06:57

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