Commit 0476b7ccdbf13b9821777f6359c03ce68031e462
1 parent
3f8dd2b3
Refactor `QPDF_linearization.cc` to streamline logic with `util::assertion`, rep…
…lace manual checks with `no_ci_stop_if`, and utilize `emplace_back` for improved clarity.
Showing
2 changed files
with
179 additions
and
181 deletions
libqpdf/QPDF_linearization.cc
| ... | ... | @@ -39,9 +39,9 @@ load_vector_int( |
| 39 | 39 | } |
| 40 | 40 | vec.at(i).*field = bit_stream.getBitsInt(QIntC::to_size(bits_wanted)); |
| 41 | 41 | } |
| 42 | - if (QIntC::to_int(vec.size()) != nitems) { | |
| 43 | - throw std::logic_error("vector has wrong size in load_vector_int"); | |
| 44 | - } | |
| 42 | + util::assertion( | |
| 43 | + std::cmp_equal(vec.size(), nitems), "vector has wrong size in load_vector_int" // | |
| 44 | + ); | |
| 45 | 45 | // The PDF spec says that each hint table starts at a byte boundary. Each "row" actually must |
| 46 | 46 | // start on a byte boundary. |
| 47 | 47 | bit_stream.skipToNextByte(); |
| ... | ... | @@ -142,13 +142,13 @@ QPDF::isLinearized() |
| 142 | 142 | void |
| 143 | 143 | QPDF::readLinearizationData() |
| 144 | 144 | { |
| 145 | + util::assertion( | |
| 146 | + isLinearized(), "called readLinearizationData for file that is not linearized" // | |
| 147 | + ); | |
| 148 | + | |
| 145 | 149 | // This function throws an exception (which is trapped by checkLinearization()) for any errors |
| 146 | 150 | // that prevent loading. |
| 147 | 151 | |
| 148 | - if (!isLinearized()) { | |
| 149 | - throw std::logic_error("called readLinearizationData for file that is not linearized"); | |
| 150 | - } | |
| 151 | - | |
| 152 | 152 | // /L is read and stored in linp by isLinearized() |
| 153 | 153 | Array H = m->lindict["/H"]; // hint table offset/length for primary and overflow hint tables |
| 154 | 154 | auto H_size = H.size(); |
| ... | ... | @@ -164,27 +164,33 @@ QPDF::readLinearizationData() |
| 164 | 164 | Integer P = P_oh; // first page number |
| 165 | 165 | QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1); |
| 166 | 166 | |
| 167 | - if (!(H && O && E && N && T && (P || P_oh.null()))) { | |
| 168 | - throw damagedPDF( | |
| 169 | - "linearization dictionary", | |
| 170 | - "some keys in linearization dictionary are of the wrong type"); | |
| 171 | - } | |
| 167 | + no_ci_stop_if( | |
| 168 | + !(H && O && E && N && T && (P || P_oh.null())), | |
| 169 | + "some keys in linearization dictionary are of the wrong type", | |
| 170 | + "linearization dictionary" // | |
| 171 | + ); | |
| 172 | 172 | |
| 173 | - if (!(H_size == 2 || H_size == 4)) { | |
| 174 | - throw damagedPDF("linearization dictionary", "H has the wrong number of items"); | |
| 175 | - } | |
| 173 | + no_ci_stop_if( | |
| 174 | + !(H_size == 2 || H_size == 4), | |
| 175 | + "H has the wrong number of items", | |
| 176 | + "linearization dictionary" // | |
| 177 | + ); | |
| 176 | 178 | |
| 177 | - if (!(H_0 && H_1 && (H_size == 2 || (H_2 && H_3)))) { | |
| 178 | - throw damagedPDF("linearization dictionary", "some H items are of the wrong type"); | |
| 179 | - } | |
| 179 | + no_ci_stop_if( | |
| 180 | + !(H_0 && H_1 && (H_size == 2 || (H_2 && H_3))), | |
| 181 | + "some H items are of the wrong type", | |
| 182 | + "linearization dictionary" // | |
| 183 | + ); | |
| 180 | 184 | |
| 181 | 185 | // Store linearization parameter data |
| 182 | 186 | |
| 183 | 187 | // Various places in the code use linp.npages, which is initialized from N, to pre-allocate |
| 184 | 188 | // memory, so make sure it's accurate and bail right now if it's not. |
| 185 | - if (N != getAllPages().size()) { | |
| 186 | - throw damagedPDF("linearization hint table", "/N does not match number of pages"); | |
| 187 | - } | |
| 189 | + no_ci_stop_if( | |
| 190 | + N != getAllPages().size(), | |
| 191 | + "/N does not match number of pages", | |
| 192 | + "linearization dictionary" // | |
| 193 | + ); | |
| 188 | 194 | |
| 189 | 195 | // file_size initialized by isLinearized() |
| 190 | 196 | m->linp.first_page_object = O; |
| ... | ... | @@ -231,9 +237,11 @@ QPDF::readLinearizationData() |
| 231 | 237 | readHSharedObject(BitStream(h_buf + HSi, h_size - HSi)); |
| 232 | 238 | |
| 233 | 239 | if (HO) { |
| 234 | - if (HO < 0 || HO >= h_size) { | |
| 235 | - throw damagedPDF("linearization hint table", "/O (outline) offset is out of bounds"); | |
| 236 | - } | |
| 240 | + no_ci_stop_if( | |
| 241 | + HO < 0 || HO >= h_size, | |
| 242 | + "/O (outline) offset is out of bounds", | |
| 243 | + "linearization dictionary" // | |
| 244 | + ); | |
| 237 | 245 | size_t HOi = HO; |
| 238 | 246 | readHGeneric(BitStream(h_buf + HO, h_size - HOi), m->outline_hints); |
| 239 | 247 | } |
| ... | ... | @@ -246,9 +254,9 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) |
| 246 | 254 | ObjCache& oc = m->obj_cache[H]; |
| 247 | 255 | qpdf_offset_t min_end_offset = oc.end_before_space; |
| 248 | 256 | qpdf_offset_t max_end_offset = oc.end_after_space; |
| 249 | - if (!H.isStream()) { | |
| 250 | - throw damagedPDF("linearization dictionary", "hint table is not a stream"); | |
| 251 | - } | |
| 257 | + no_ci_stop_if( | |
| 258 | + !H.isStream(), "hint table is not a stream", "linearization dictionary" // | |
| 259 | + ); | |
| 252 | 260 | |
| 253 | 261 | Dictionary Hdict = H.getDict(); |
| 254 | 262 | |
| ... | ... | @@ -264,12 +272,12 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) |
| 264 | 272 | QTC::TC("qpdf", "QPDF hint table length direct"); |
| 265 | 273 | } |
| 266 | 274 | qpdf_offset_t computed_end = offset + toO(length); |
| 267 | - if ((computed_end < min_end_offset) || (computed_end > max_end_offset)) { | |
| 268 | - linearizationWarning( | |
| 269 | - "expected = " + std::to_string(computed_end) + | |
| 270 | - "; actual = " + std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset)); | |
| 271 | - throw damagedPDF("linearization dictionary", "hint table length mismatch"); | |
| 272 | - } | |
| 275 | + no_ci_stop_if( | |
| 276 | + computed_end < min_end_offset || computed_end > max_end_offset, | |
| 277 | + "hint table length mismatch (expected = " + std::to_string(computed_end) + "; actual = " + | |
| 278 | + std::to_string(min_end_offset) + ".." + std::to_string(max_end_offset) + ")", | |
| 279 | + "linearization dictionary" // | |
| 280 | + ); | |
| 273 | 281 | H.pipeStreamData(&pl, 0, qpdf_dl_specialized); |
| 274 | 282 | return Hdict; |
| 275 | 283 | } |
| ... | ... | @@ -382,7 +390,6 @@ QPDF::checkLinearizationInternal() |
| 382 | 390 | // O: object number of first page |
| 383 | 391 | std::vector<QPDFObjectHandle> const& pages = getAllPages(); |
| 384 | 392 | if (p.first_page_object != pages.at(0).getObjectID()) { |
| 385 | - QTC::TC("qpdf", "QPDF err /O mismatch"); | |
| 386 | 393 | linearizationWarning("first page object (/O) mismatch"); |
| 387 | 394 | } |
| 388 | 395 | |
| ... | ... | @@ -393,13 +400,13 @@ QPDF::checkLinearizationInternal() |
| 393 | 400 | linearizationWarning("page count (/N) mismatch"); |
| 394 | 401 | } |
| 395 | 402 | |
| 396 | - for (size_t i = 0; i < npages; ++i) { | |
| 397 | - QPDFObjectHandle const& page = pages.at(i); | |
| 398 | - QPDFObjGen og(page.getObjGen()); | |
| 399 | - if (m->xref_table[og].getType() == 2) { | |
| 403 | + int i = 0; | |
| 404 | + for (auto const& page: pages) { | |
| 405 | + if (m->xref_table[page].getType() == 2) { | |
| 400 | 406 | linearizationWarning( |
| 401 | 407 | "page dictionary for page " + std::to_string(i) + " is compressed"); |
| 402 | 408 | } |
| 409 | + ++i; | |
| 403 | 410 | } |
| 404 | 411 | |
| 405 | 412 | // T: offset of whitespace character preceding xref entry for object 0 |
| ... | ... | @@ -407,13 +414,12 @@ QPDF::checkLinearizationInternal() |
| 407 | 414 | while (true) { |
| 408 | 415 | char ch; |
| 409 | 416 | m->file->read(&ch, 1); |
| 410 | - if (!((ch == ' ') || (ch == '\r') || (ch == '\n'))) { | |
| 417 | + if (!(ch == ' ' || ch == '\r' || ch == '\n')) { | |
| 411 | 418 | m->file->seek(-1, SEEK_CUR); |
| 412 | 419 | break; |
| 413 | 420 | } |
| 414 | 421 | } |
| 415 | 422 | if (m->file->tell() != m->first_xref_item_offset) { |
| 416 | - QTC::TC("qpdf", "QPDF err /T mismatch"); | |
| 417 | 423 | linearizationWarning( |
| 418 | 424 | "space before first xref item (/T) mismatch (computed = " + |
| 419 | 425 | std::to_string(m->first_xref_item_offset) + |
| ... | ... | @@ -438,9 +444,7 @@ QPDF::checkLinearizationInternal() |
| 438 | 444 | // to figure out which objects are compressed and which are uncompressed. |
| 439 | 445 | { // local scope |
| 440 | 446 | std::map<int, int> object_stream_data; |
| 441 | - for (auto const& iter: m->xref_table) { | |
| 442 | - QPDFObjGen const& og = iter.first; | |
| 443 | - QPDFXRefEntry const& entry = iter.second; | |
| 447 | + for (auto const& [og, entry]: m->xref_table) { | |
| 444 | 448 | if (entry.getType() == 2) { |
| 445 | 449 | object_stream_data[og.getObj()] = entry.getObjStreamNumber(); |
| 446 | 450 | } |
| ... | ... | @@ -457,23 +461,20 @@ QPDF::checkLinearizationInternal() |
| 457 | 461 | // are present. In that case, it would probably agree with pdlin. As of this writing, the test |
| 458 | 462 | // suite doesn't contain any files with threads. |
| 459 | 463 | |
| 460 | - if (m->part6.empty()) { | |
| 461 | - stopOnError("linearization part 6 unexpectedly empty"); | |
| 462 | - } | |
| 464 | + no_ci_stop_if( | |
| 465 | + m->part6.empty(), "linearization part 6 unexpectedly empty" // | |
| 466 | + ); | |
| 463 | 467 | qpdf_offset_t min_E = -1; |
| 464 | 468 | qpdf_offset_t max_E = -1; |
| 465 | 469 | for (auto const& oh: m->part6) { |
| 466 | 470 | QPDFObjGen og(oh.getObjGen()); |
| 467 | - if (!m->obj_cache.contains(og)) { | |
| 468 | - // All objects have to have been dereferenced to be classified. | |
| 469 | - throw std::logic_error("linearization part6 object not in cache"); | |
| 470 | - } | |
| 471 | + // All objects have to have been dereferenced to be classified. | |
| 472 | + util::assertion(m->obj_cache.contains(og), "linearization part6 object not in cache"); | |
| 471 | 473 | ObjCache const& oc = m->obj_cache[og]; |
| 472 | 474 | min_E = std::max(min_E, oc.end_before_space); |
| 473 | 475 | max_E = std::max(max_E, oc.end_after_space); |
| 474 | 476 | } |
| 475 | - if ((p.first_page_end < min_E) || (p.first_page_end > max_E)) { | |
| 476 | - QTC::TC("qpdf", "QPDF warn /E mismatch"); | |
| 477 | + if (p.first_page_end < min_E || p.first_page_end > max_E) { | |
| 477 | 478 | linearizationWarning( |
| 478 | 479 | "end of first page section (/E) mismatch: /E = " + std::to_string(p.first_page_end) + |
| 479 | 480 | "; computed = " + std::to_string(min_E) + ".." + std::to_string(max_E)); |
| ... | ... | @@ -490,14 +491,16 @@ QPDF::checkLinearizationInternal() |
| 490 | 491 | qpdf_offset_t |
| 491 | 492 | QPDF::maxEnd(ObjUser const& ou) |
| 492 | 493 | { |
| 493 | - if (!m->obj_user_to_objects.contains(ou)) { | |
| 494 | - stopOnError("no entry in object user table for requested object user"); | |
| 495 | - } | |
| 494 | + no_ci_stop_if( | |
| 495 | + !m->obj_user_to_objects.contains(ou), | |
| 496 | + "no entry in object user table for requested object user" // | |
| 497 | + ); | |
| 498 | + | |
| 496 | 499 | qpdf_offset_t end = 0; |
| 497 | 500 | for (auto const& og: m->obj_user_to_objects[ou]) { |
| 498 | - if (!m->obj_cache.contains(og)) { | |
| 499 | - stopOnError("unknown object referenced in object user table"); | |
| 500 | - } | |
| 501 | + no_ci_stop_if( | |
| 502 | + !m->obj_cache.contains(og), "unknown object referenced in object user table" // | |
| 503 | + ); | |
| 501 | 504 | end = std::max(end, m->obj_cache[og].end_after_space); |
| 502 | 505 | } |
| 503 | 506 | return end; |
| ... | ... | @@ -506,34 +509,25 @@ QPDF::maxEnd(ObjUser const& ou) |
| 506 | 509 | qpdf_offset_t |
| 507 | 510 | QPDF::getLinearizationOffset(QPDFObjGen og) |
| 508 | 511 | { |
| 509 | - QPDFXRefEntry entry = m->xref_table[og]; | |
| 510 | - qpdf_offset_t result = 0; | |
| 511 | - switch (entry.getType()) { | |
| 512 | - case 1: | |
| 513 | - result = entry.getOffset(); | |
| 514 | - break; | |
| 515 | - | |
| 516 | - case 2: | |
| 517 | - // For compressed objects, return the offset of the object stream that contains them. | |
| 518 | - result = getLinearizationOffset(QPDFObjGen(entry.getObjStreamNumber(), 0)); | |
| 519 | - break; | |
| 520 | - | |
| 521 | - default: | |
| 522 | - stopOnError("getLinearizationOffset called for xref entry not of type 1 or 2"); | |
| 523 | - break; | |
| 524 | - } | |
| 525 | - return result; | |
| 512 | + QPDFXRefEntry const& entry = m->xref_table[og]; | |
| 513 | + auto typ = entry.getType(); | |
| 514 | + if (typ == 1) { | |
| 515 | + return entry.getOffset(); | |
| 516 | + } | |
| 517 | + no_ci_stop_if( | |
| 518 | + typ != 2, "getLinearizationOffset called for xref entry not of type 1 or 2" // | |
| 519 | + ); | |
| 520 | + // For compressed objects, return the offset of the object stream that contains them. | |
| 521 | + return getLinearizationOffset({entry.getObjStreamNumber(), 0}); | |
| 526 | 522 | } |
| 527 | 523 | |
| 528 | 524 | QPDFObjectHandle |
| 529 | 525 | QPDF::getUncompressedObject(QPDFObjectHandle& obj, std::map<int, int> const& object_stream_data) |
| 530 | 526 | { |
| 531 | - if (obj.null() || (!object_stream_data.contains(obj.getObjectID()))) { | |
| 527 | + if (obj.null() || !object_stream_data.contains(obj.getObjectID())) { | |
| 532 | 528 | return obj; |
| 533 | - } else { | |
| 534 | - int repl = (*(object_stream_data.find(obj.getObjectID()))).second; | |
| 535 | - return getObject(repl, 0); | |
| 536 | 529 | } |
| 530 | + return getObject((*(object_stream_data.find(obj.getObjectID()))).second, 0); | |
| 537 | 531 | } |
| 538 | 532 | |
| 539 | 533 | QPDFObjectHandle |
| ... | ... | @@ -553,14 +547,16 @@ QPDF::lengthNextN(int first_object, int n) |
| 553 | 547 | int length = 0; |
| 554 | 548 | for (int i = 0; i < n; ++i) { |
| 555 | 549 | QPDFObjGen og(first_object + i, 0); |
| 556 | - if (!m->xref_table.contains(og)) { | |
| 550 | + if (m->xref_table.contains(og)) { | |
| 551 | + no_ci_stop_if( | |
| 552 | + !m->obj_cache.contains(og), | |
| 553 | + "found unknown object while calculating length for linearization data" // | |
| 554 | + ); | |
| 555 | + | |
| 556 | + length += toI(m->obj_cache[og].end_after_space - getLinearizationOffset(og)); | |
| 557 | + } else { | |
| 557 | 558 | linearizationWarning( |
| 558 | 559 | "no xref table entry for " + std::to_string(first_object + i) + " 0"); |
| 559 | - } else { | |
| 560 | - if (!m->obj_cache.contains(og)) { | |
| 561 | - stopOnError("found unknown object while calculating length for linearization data"); | |
| 562 | - } | |
| 563 | - length += toI(m->obj_cache[og].end_after_space - getLinearizationOffset(og)); | |
| 564 | 560 | } |
| 565 | 561 | } |
| 566 | 562 | return length; |
| ... | ... | @@ -629,7 +625,7 @@ QPDF::checkHPageOffset( |
| 629 | 625 | std::set<int> hint_shared; |
| 630 | 626 | std::set<int> computed_shared; |
| 631 | 627 | |
| 632 | - if ((pageno == 0) && (he.nshared_objects > 0)) { | |
| 628 | + if (pageno == 0 && he.nshared_objects > 0) { | |
| 633 | 629 | // pdlin and Acrobat both do this even though the spec states clearly and unambiguously |
| 634 | 630 | // that they should not. |
| 635 | 631 | linearizationWarning("page 0 has shared identifier entries"); |
| ... | ... | @@ -637,17 +633,20 @@ QPDF::checkHPageOffset( |
| 637 | 633 | |
| 638 | 634 | for (size_t i = 0; i < toS(he.nshared_objects); ++i) { |
| 639 | 635 | int idx = he.shared_identifiers.at(i); |
| 640 | - if (!shared_idx_to_obj.contains(idx)) { | |
| 641 | - stopOnError("unable to get object for item in shared objects hint table"); | |
| 642 | - } | |
| 636 | + no_ci_stop_if( | |
| 637 | + !shared_idx_to_obj.contains(idx), | |
| 638 | + "unable to get object for item in shared objects hint table"); | |
| 639 | + | |
| 643 | 640 | hint_shared.insert(shared_idx_to_obj[idx]); |
| 644 | 641 | } |
| 645 | 642 | |
| 646 | 643 | for (size_t i = 0; i < toS(ce.nshared_objects); ++i) { |
| 647 | 644 | int idx = ce.shared_identifiers.at(i); |
| 648 | - if (idx >= m->c_shared_object_data.nshared_total) { | |
| 649 | - stopOnError("index out of bounds for shared object hint table"); | |
| 650 | - } | |
| 645 | + no_ci_stop_if( | |
| 646 | + idx >= m->c_shared_object_data.nshared_total, | |
| 647 | + "index out of bounds for shared object hint table" // | |
| 648 | + ); | |
| 649 | + | |
| 651 | 650 | int obj = m->c_shared_object_data.entries.at(toS(idx)).object; |
| 652 | 651 | computed_shared.insert(obj); |
| 653 | 652 | } |
| ... | ... | @@ -766,9 +765,9 @@ QPDF::checkHOutlines() |
| 766 | 765 | return; |
| 767 | 766 | } |
| 768 | 767 | QPDFObjGen og(outlines.getObjGen()); |
| 769 | - if (!m->xref_table.contains(og)) { | |
| 770 | - stopOnError("unknown object in outlines hint table"); | |
| 771 | - } | |
| 768 | + no_ci_stop_if( | |
| 769 | + !m->xref_table.contains(og), "unknown object in outlines hint table" // | |
| 770 | + ); | |
| 772 | 771 | qpdf_offset_t offset = getLinearizationOffset(og); |
| 773 | 772 | ObjUser ou(ObjUser::ou_root_key, "/Outlines"); |
| 774 | 773 | int length = toI(maxEnd(ou) - offset); |
| ... | ... | @@ -926,12 +925,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 926 | 925 | // file must be optimized (via calling optimize()) prior to calling this function. Note that |
| 927 | 926 | // actual offsets and lengths are not computed here, but anything related to object ordering is. |
| 928 | 927 | |
| 929 | - if (m->object_to_obj_users.empty()) { | |
| 930 | - // Note that we can't call optimize here because we don't know whether it should be called | |
| 931 | - // with or without allow changes. | |
| 932 | - throw std::logic_error( | |
| 933 | - "INTERNAL ERROR: QPDF::calculateLinearizationData called before optimize()"); | |
| 934 | - } | |
| 928 | + util::assertion( | |
| 929 | + !m->object_to_obj_users.empty(), | |
| 930 | + "INTERNAL ERROR: QPDF::calculateLinearizationData called before optimize()" // | |
| 931 | + ); | |
| 932 | + // Note that we can't call optimize here because we don't know whether it should be called | |
| 933 | + // with or without allow changes. | |
| 935 | 934 | |
| 936 | 935 | // Separate objects into the categories sufficient for us to determine which part of the |
| 937 | 936 | // linearized file should contain the object. This categorization is useful for other purposes |
| ... | ... | @@ -1108,7 +1107,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1108 | 1107 | // Map all page objects to the containing object stream. This should be a no-op in a |
| 1109 | 1108 | // properly linearized file. |
| 1110 | 1109 | for (auto oh: getAllPages()) { |
| 1111 | - pages.push_back(getUncompressedObject(oh, object_stream_data)); | |
| 1110 | + pages.emplace_back(getUncompressedObject(oh, object_stream_data)); | |
| 1112 | 1111 | } |
| 1113 | 1112 | } |
| 1114 | 1113 | size_t npages = pages.size(); |
| ... | ... | @@ -1126,12 +1125,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1126 | 1125 | |
| 1127 | 1126 | // Part 4: open document objects. We don't care about the order. |
| 1128 | 1127 | |
| 1129 | - if (lc_root.size() != 1) { | |
| 1130 | - stopOnError("found other than one root while calculating linearization data"); | |
| 1131 | - } | |
| 1132 | - m->part4.push_back(getObject(*(lc_root.begin()))); | |
| 1128 | + no_ci_stop_if( | |
| 1129 | + lc_root.size() != 1, "found other than one root while calculating linearization data" // | |
| 1130 | + ); | |
| 1131 | + | |
| 1132 | + m->part4.emplace_back(getObject(*(lc_root.begin()))); | |
| 1133 | 1133 | for (auto const& og: lc_open_document) { |
| 1134 | - m->part4.push_back(getObject(og)); | |
| 1134 | + m->part4.emplace_back(getObject(og)); | |
| 1135 | 1135 | } |
| 1136 | 1136 | |
| 1137 | 1137 | // Part 6: first page objects. Note: implementation note 124 states that Acrobat always treats |
| ... | ... | @@ -1139,29 +1139,26 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1139 | 1139 | // any option to set this and also disregards /OpenAction. We will do the same. |
| 1140 | 1140 | |
| 1141 | 1141 | // First, place the actual first page object itself. |
| 1142 | - if (pages.empty()) { | |
| 1143 | - stopOnError("no pages found while calculating linearization data"); | |
| 1144 | - } | |
| 1142 | + no_ci_stop_if( | |
| 1143 | + pages.empty(), "no pages found while calculating linearization data" // | |
| 1144 | + ); | |
| 1145 | 1145 | QPDFObjGen first_page_og(pages.at(0).getObjGen()); |
| 1146 | - if (!lc_first_page_private.contains(first_page_og)) { | |
| 1147 | - stopOnError( | |
| 1148 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: first page " | |
| 1149 | - "object not in lc_first_page_private"); | |
| 1150 | - } | |
| 1151 | - lc_first_page_private.erase(first_page_og); | |
| 1146 | + no_ci_stop_if( | |
| 1147 | + !lc_first_page_private.erase(first_page_og), "unable to linearize first page" // | |
| 1148 | + ); | |
| 1152 | 1149 | m->c_linp.first_page_object = pages.at(0).getObjectID(); |
| 1153 | - m->part6.push_back(pages.at(0)); | |
| 1150 | + m->part6.emplace_back(pages.at(0)); | |
| 1154 | 1151 | |
| 1155 | 1152 | // The PDF spec "recommends" an order for the rest of the objects, but we are going to disregard |
| 1156 | 1153 | // it except to the extent that it groups private and shared objects contiguously for the sake |
| 1157 | 1154 | // of hint tables. |
| 1158 | 1155 | |
| 1159 | 1156 | for (auto const& og: lc_first_page_private) { |
| 1160 | - m->part6.push_back(getObject(og)); | |
| 1157 | + m->part6.emplace_back(getObject(og)); | |
| 1161 | 1158 | } |
| 1162 | 1159 | |
| 1163 | 1160 | for (auto const& og: lc_first_page_shared) { |
| 1164 | - m->part6.push_back(getObject(og)); | |
| 1161 | + m->part6.emplace_back(getObject(og)); | |
| 1165 | 1162 | } |
| 1166 | 1163 | |
| 1167 | 1164 | // Place the outline dictionary if it goes in the first page section. |
| ... | ... | @@ -1182,13 +1179,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1182 | 1179 | // Place this page's page object |
| 1183 | 1180 | |
| 1184 | 1181 | QPDFObjGen page_og(pages.at(i).getObjGen()); |
| 1185 | - if (!lc_other_page_private.contains(page_og)) { | |
| 1186 | - stopOnError( | |
| 1187 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: page object for page " + | |
| 1188 | - std::to_string(i) + " not in lc_other_page_private"); | |
| 1189 | - } | |
| 1190 | - lc_other_page_private.erase(page_og); | |
| 1191 | - m->part7.push_back(pages.at(i)); | |
| 1182 | + no_ci_stop_if( | |
| 1183 | + !lc_other_page_private.erase(page_og), | |
| 1184 | + "unable to linearize page " + std::to_string(i) // | |
| 1185 | + ); | |
| 1186 | + | |
| 1187 | + m->part7.emplace_back(pages.at(i)); | |
| 1192 | 1188 | |
| 1193 | 1189 | // Place all non-shared objects referenced by this page, updating the page object count for |
| 1194 | 1190 | // the hint table. |
| ... | ... | @@ -1196,29 +1192,30 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1196 | 1192 | m->c_page_offset_data.entries.at(i).nobjects = 1; |
| 1197 | 1193 | |
| 1198 | 1194 | ObjUser ou(ObjUser::ou_page, i); |
| 1199 | - if (!m->obj_user_to_objects.contains(ou)) { | |
| 1200 | - stopOnError("found unreferenced page while calculating linearization data"); | |
| 1201 | - } | |
| 1195 | + no_ci_stop_if( | |
| 1196 | + !m->obj_user_to_objects.contains(ou), | |
| 1197 | + "found unreferenced page while calculating linearization data" // | |
| 1198 | + ); | |
| 1199 | + | |
| 1202 | 1200 | for (auto const& og: m->obj_user_to_objects[ou]) { |
| 1203 | - if (lc_other_page_private.contains(og)) { | |
| 1204 | - lc_other_page_private.erase(og); | |
| 1205 | - m->part7.push_back(getObject(og)); | |
| 1201 | + if (lc_other_page_private.erase(og)) { | |
| 1202 | + m->part7.emplace_back(getObject(og)); | |
| 1206 | 1203 | ++m->c_page_offset_data.entries.at(i).nobjects; |
| 1207 | 1204 | } |
| 1208 | 1205 | } |
| 1209 | 1206 | } |
| 1210 | 1207 | // That should have covered all part7 objects. |
| 1211 | - if (!lc_other_page_private.empty()) { | |
| 1212 | - stopOnError( | |
| 1213 | - "INTERNAL ERROR: QPDF::calculateLinearizationData:" | |
| 1214 | - " lc_other_page_private is not empty after generation of part7"); | |
| 1215 | - } | |
| 1208 | + util::assertion( | |
| 1209 | + lc_other_page_private.empty(), | |
| 1210 | + "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_other_page_private is not empty " | |
| 1211 | + "after generation of part7" // | |
| 1212 | + ); | |
| 1216 | 1213 | |
| 1217 | 1214 | // Part 8: other pages' shared objects |
| 1218 | 1215 | |
| 1219 | 1216 | // Order is unimportant. |
| 1220 | 1217 | for (auto const& og: lc_other_page_shared) { |
| 1221 | - m->part8.push_back(getObject(og)); | |
| 1218 | + m->part8.emplace_back(getObject(og)); | |
| 1222 | 1219 | } |
| 1223 | 1220 | |
| 1224 | 1221 | // Part 9: other objects |
| ... | ... | @@ -1231,13 +1228,12 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1231 | 1228 | // Place the pages tree. |
| 1232 | 1229 | std::set<QPDFObjGen> pages_ogs = |
| 1233 | 1230 | m->obj_user_to_objects[ObjUser(ObjUser::ou_root_key, "/Pages")]; |
| 1234 | - if (pages_ogs.empty()) { | |
| 1235 | - stopOnError("found empty pages tree while calculating linearization data"); | |
| 1236 | - } | |
| 1231 | + no_ci_stop_if( | |
| 1232 | + pages_ogs.empty(), "found empty pages tree while calculating linearization data" // | |
| 1233 | + ); | |
| 1237 | 1234 | for (auto const& og: pages_ogs) { |
| 1238 | - if (lc_other.contains(og)) { | |
| 1239 | - lc_other.erase(og); | |
| 1240 | - m->part9.push_back(getObject(og)); | |
| 1235 | + if (lc_other.erase(og)) { | |
| 1236 | + m->part9.emplace_back(getObject(og)); | |
| 1241 | 1237 | } |
| 1242 | 1238 | } |
| 1243 | 1239 | |
| ... | ... | @@ -1263,15 +1259,15 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1263 | 1259 | } |
| 1264 | 1260 | } |
| 1265 | 1261 | } |
| 1266 | - if (!lc_thumbnail_private.empty()) { | |
| 1267 | - stopOnError( | |
| 1268 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_thumbnail_private not " | |
| 1269 | - "empty after placing thumbnails"); | |
| 1270 | - } | |
| 1262 | + util::assertion( | |
| 1263 | + lc_thumbnail_private.empty(), | |
| 1264 | + "INTERNAL ERROR: QPDF::calculateLinearizationData: lc_thumbnail_private not " | |
| 1265 | + "empty after placing thumbnails" // | |
| 1266 | + ); | |
| 1271 | 1267 | |
| 1272 | 1268 | // Place shared thumbnail objects |
| 1273 | 1269 | for (auto const& og: lc_thumbnail_shared) { |
| 1274 | - m->part9.push_back(getObject(og)); | |
| 1270 | + m->part9.emplace_back(getObject(og)); | |
| 1275 | 1271 | } |
| 1276 | 1272 | |
| 1277 | 1273 | // Place outlines unless in first page |
| ... | ... | @@ -1281,7 +1277,7 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1281 | 1277 | |
| 1282 | 1278 | // Place all remaining objects |
| 1283 | 1279 | for (auto const& og: lc_other) { |
| 1284 | - m->part9.push_back(getObject(og)); | |
| 1280 | + m->part9.emplace_back(getObject(og)); | |
| 1285 | 1281 | } |
| 1286 | 1282 | |
| 1287 | 1283 | // Make sure we got everything exactly once. |
| ... | ... | @@ -1289,12 +1285,13 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1289 | 1285 | size_t num_placed = |
| 1290 | 1286 | m->part4.size() + m->part6.size() + m->part7.size() + m->part8.size() + m->part9.size(); |
| 1291 | 1287 | size_t num_wanted = m->object_to_obj_users.size(); |
| 1292 | - if (num_placed != num_wanted) { | |
| 1293 | - stopOnError( | |
| 1294 | - "INTERNAL ERROR: QPDF::calculateLinearizationData: wrong " | |
| 1295 | - "number of objects placed (num_placed = " + | |
| 1296 | - std::to_string(num_placed) + "; number of objects: " + std::to_string(num_wanted)); | |
| 1297 | - } | |
| 1288 | + no_ci_stop_if( | |
| 1289 | + // This can happen with damaged files, e.g. if the root is part of the the pages tree. | |
| 1290 | + num_placed != num_wanted, | |
| 1291 | + "QPDF::calculateLinearizationData: wrong number of objects placed (num_placed = " + | |
| 1292 | + std::to_string(num_placed) + "; number of objects: " + std::to_string(num_wanted) + | |
| 1293 | + "\nIf the file did not generate any other warnings please report this as a bug." // | |
| 1294 | + ); | |
| 1298 | 1295 | |
| 1299 | 1296 | // Calculate shared object hint table information including references to shared objects from |
| 1300 | 1297 | // page offset hint data. |
| ... | ... | @@ -1326,19 +1323,22 @@ QPDF::calculateLinearizationData(T const& object_stream_data) |
| 1326 | 1323 | shared.emplace_back(obj); |
| 1327 | 1324 | } |
| 1328 | 1325 | } |
| 1329 | - if (static_cast<size_t>(m->c_shared_object_data.nshared_total) != | |
| 1330 | - m->c_shared_object_data.entries.size()) { | |
| 1331 | - stopOnError("shared object hint table has wrong number of entries"); | |
| 1332 | - } | |
| 1326 | + no_ci_stop_if( | |
| 1327 | + std::cmp_not_equal( | |
| 1328 | + m->c_shared_object_data.nshared_total, m->c_shared_object_data.entries.size()), | |
| 1329 | + "shared object hint table has wrong number of entries" // | |
| 1330 | + ); | |
| 1333 | 1331 | |
| 1334 | 1332 | // Now compute the list of shared objects for each page after the first page. |
| 1335 | 1333 | |
| 1336 | 1334 | for (size_t i = 1; i < npages; ++i) { |
| 1337 | 1335 | CHPageOffsetEntry& pe = m->c_page_offset_data.entries.at(i); |
| 1338 | 1336 | ObjUser ou(ObjUser::ou_page, i); |
| 1339 | - if (!m->obj_user_to_objects.contains(ou)) { | |
| 1340 | - stopOnError("found unreferenced page while calculating linearization data"); | |
| 1341 | - } | |
| 1337 | + no_ci_stop_if( | |
| 1338 | + !m->obj_user_to_objects.contains(ou), | |
| 1339 | + "found unreferenced page while calculating linearization data" // | |
| 1340 | + ); | |
| 1341 | + | |
| 1342 | 1342 | for (auto const& og: m->obj_user_to_objects[ou]) { |
| 1343 | 1343 | if ((m->object_to_obj_users[og].size() > 1) && (obj_to_index.contains(og.getObj()))) { |
| 1344 | 1344 | int idx = obj_to_index[og.getObj()]; |
| ... | ... | @@ -1372,7 +1372,7 @@ QPDF::pushOutlinesToPart( |
| 1372 | 1372 | if (lc_outlines.erase(outlines_og)) { |
| 1373 | 1373 | // Make sure outlines is in lc_outlines in case the file is damaged. in which case it may be |
| 1374 | 1374 | // included in an earlier part. |
| 1375 | - part.push_back(outlines); | |
| 1375 | + part.emplace_back(outlines); | |
| 1376 | 1376 | m->c_outline_data.first_object = outlines_og.getObj(); |
| 1377 | 1377 | m->c_outline_data.nobjects = 1; |
| 1378 | 1378 | } |
| ... | ... | @@ -1380,7 +1380,7 @@ QPDF::pushOutlinesToPart( |
| 1380 | 1380 | if (!m->c_outline_data.first_object) { |
| 1381 | 1381 | m->c_outline_data.first_object = og.getObj(); |
| 1382 | 1382 | } |
| 1383 | - part.push_back(getObject(og)); | |
| 1383 | + part.emplace_back(getObject(og)); | |
| 1384 | 1384 | ++m->c_outline_data.nobjects; |
| 1385 | 1385 | } |
| 1386 | 1386 | } |
| ... | ... | @@ -1417,15 +1417,14 @@ QPDF::outputLengthNextN( |
| 1417 | 1417 | |
| 1418 | 1418 | int first = obj[in_object].renumber; |
| 1419 | 1419 | int last = first + n; |
| 1420 | - if (first <= 0) { | |
| 1421 | - stopOnError("found object that is not renumbered while writing linearization data"); | |
| 1422 | - } | |
| 1420 | + no_ci_stop_if( | |
| 1421 | + first <= 0, "found object that is not renumbered while writing linearization data"); | |
| 1423 | 1422 | qpdf_offset_t length = 0; |
| 1424 | 1423 | for (int i = first; i < last; ++i) { |
| 1425 | 1424 | auto l = new_obj[i].length; |
| 1426 | - if (l == 0) { | |
| 1427 | - stopOnError("found item with unknown length while writing linearization data"); | |
| 1428 | - } | |
| 1425 | + no_ci_stop_if( | |
| 1426 | + l == 0, "found item with unknown length while writing linearization data" // | |
| 1427 | + ); | |
| 1429 | 1428 | length += l; |
| 1430 | 1429 | } |
| 1431 | 1430 | return toI(length); |
| ... | ... | @@ -1536,9 +1535,9 @@ QPDF::calculateHSharedObject( |
| 1536 | 1535 | soe.emplace_back(); |
| 1537 | 1536 | soe.at(i).delta_group_length = length; |
| 1538 | 1537 | } |
| 1539 | - if (soe.size() != toS(cso.nshared_total)) { | |
| 1540 | - stopOnError("soe has wrong size after initialization"); | |
| 1541 | - } | |
| 1538 | + no_ci_stop_if( | |
| 1539 | + soe.size() != toS(cso.nshared_total), "soe has wrong size after initialization" // | |
| 1540 | + ); | |
| 1542 | 1541 | |
| 1543 | 1542 | so.nshared_total = cso.nshared_total; |
| 1544 | 1543 | so.nshared_first_page = cso.nshared_first_page; |
| ... | ... | @@ -1552,9 +1551,11 @@ QPDF::calculateHSharedObject( |
| 1552 | 1551 | |
| 1553 | 1552 | for (size_t i = 0; i < toS(cso.nshared_total); ++i) { |
| 1554 | 1553 | // Adjust deltas |
| 1555 | - if (soe.at(i).delta_group_length < min_length) { | |
| 1556 | - stopOnError("found too small group length while writing linearization data"); | |
| 1557 | - } | |
| 1554 | + no_ci_stop_if( | |
| 1555 | + soe.at(i).delta_group_length < min_length, | |
| 1556 | + "found too small group length while writing linearization data" // | |
| 1557 | + ); | |
| 1558 | + | |
| 1558 | 1559 | soe.at(i).delta_group_length -= min_length; |
| 1559 | 1560 | } |
| 1560 | 1561 | } | ... | ... |
qpdf/qpdf.testcov
| ... | ... | @@ -10,8 +10,6 @@ QPDF object stream contains id < 1 0 |
| 10 | 10 | QPDF hint table length direct 0 |
| 11 | 11 | QPDF P absent in lindict 1 |
| 12 | 12 | QPDF expected n n obj 0 |
| 13 | -QPDF err /T mismatch 0 | |
| 14 | -QPDF err /O mismatch 0 | |
| 15 | 13 | QPDF opt direct pages resource 1 |
| 16 | 14 | QPDF opt inheritable keys 0 |
| 17 | 15 | QPDF opt no inheritable keys 0 |
| ... | ... | @@ -22,7 +20,6 @@ QPDF opt key ancestors depth > 1 0 |
| 22 | 20 | QPDF opt loop detected 0 |
| 23 | 21 | QPDF categorize pagemode present 1 |
| 24 | 22 | QPDF categorize pagemode outlines 1 |
| 25 | -QPDF warn /E mismatch 0 | |
| 26 | 23 | QPDF lin outlines in part 1 |
| 27 | 24 | QPDF lin nshared_total > nshared_first_page 1 |
| 28 | 25 | QPDF lin part 8 empty 1 | ... | ... |