Commit cc8895078a1d64928e8ee335f1e8c7d6928de1b3

Authored by Jay Berkenbilt
1 parent 573b6eb8

Add QPDFObjectHandle::makeDirect(bool allow_streams)

ChangeLog
  1 +2020-12-22 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * Add QPDFObjectHandle::makeDirect(bool allow_streams) -- if
  4 + allow_streams is true, preserve indirect references to streams
  5 + rather than throwing an exception. This allows the object to be
  6 + made as direct as possible while preserving stream references.
  7 +
1 2020-12-20 Jay Berkenbilt <ejb@ql.org> 8 2020-12-20 Jay Berkenbilt <ejb@ql.org>
2 9
3 * Add qpdf_register_progress_reporter method to C API, 10 * Add qpdf_register_progress_reporter method to C API,
@@ -155,6 +155,8 @@ ABI Changes @@ -155,6 +155,8 @@ ABI Changes
155 This is a list of changes to make next time there is an ABI change. 155 This is a list of changes to make next time there is an ABI change.
156 Comments appear in the code prefixed by "ABI" 156 Comments appear in the code prefixed by "ABI"
157 157
  158 +* Merge two versions of QPDFObjectHandle::makeDirect per comment
  159 +
158 Page splitting/merging 160 Page splitting/merging
159 ====================== 161 ======================
160 162
include/qpdf/QPDFObjectHandle.hh
@@ -721,8 +721,20 @@ class QPDFObjectHandle @@ -721,8 +721,20 @@ class QPDFObjectHandle
721 721
722 // Mutator methods. Use with caution. 722 // Mutator methods. Use with caution.
723 723
724 - // Recursively copy this object, making it direct. Throws an  
725 - // exception if a loop is detected or any sub-object is a stream. 724 + // Recursively copy this object, making it direct. An exception is
  725 + // thrown if a loop is detected. With allow_streams true, keep
  726 + // indirect object references to streams. Otherwise, throw an
  727 + // exception if any sub-object is a stream. Note that, when
  728 + // allow_streams is true and a stream is found, the resulting
  729 + // object is still associated with the containing qpdf. When
  730 + // allow_streams is false, the object will no longer be connected
  731 + // to the original QPDF object after this call completes
  732 + // successfully.
  733 + QPDF_DLL
  734 + void makeDirect(bool allow_streams);
  735 + // Zero-arg version is equivalent to makeDirect(false).
  736 + // ABI: delete zero-arg version of makeDirect, and make
  737 + // allow_streams default to false.
726 QPDF_DLL 738 QPDF_DLL
727 void makeDirect(); 739 void makeDirect();
728 740
@@ -1121,7 +1133,7 @@ class QPDFObjectHandle @@ -1121,7 +1133,7 @@ class QPDFObjectHandle
1121 void assertType(char const* type_name, bool istype); 1133 void assertType(char const* type_name, bool istype);
1122 void dereference(); 1134 void dereference();
1123 void copyObject(std::set<QPDFObjGen>& visited, bool cross_indirect, 1135 void copyObject(std::set<QPDFObjGen>& visited, bool cross_indirect,
1124 - bool first_level_only); 1136 + bool first_level_only, bool stop_at_streams);
1125 void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only); 1137 void shallowCopyInternal(QPDFObjectHandle& oh, bool first_level_only);
1126 void releaseResolved(); 1138 void releaseResolved();
1127 static void setObjectDescriptionFromInput( 1139 static void setObjectDescriptionFromInput(
libqpdf/QPDFObjectHandle.cc
@@ -2605,18 +2605,24 @@ QPDFObjectHandle::shallowCopyInternal(QPDFObjectHandle&amp; new_obj, @@ -2605,18 +2605,24 @@ QPDFObjectHandle::shallowCopyInternal(QPDFObjectHandle&amp; new_obj,
2605 } 2605 }
2606 2606
2607 std::set<QPDFObjGen> visited; 2607 std::set<QPDFObjGen> visited;
2608 - new_obj.copyObject(visited, false, first_level_only); 2608 + new_obj.copyObject(visited, false, first_level_only, false);
2609 } 2609 }
2610 2610
2611 void 2611 void
2612 QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited, 2612 QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
2613 - bool cross_indirect, bool first_level_only) 2613 + bool cross_indirect, bool first_level_only,
  2614 + bool stop_at_streams)
2614 { 2615 {
2615 assertInitialized(); 2616 assertInitialized();
2616 2617
2617 if (isStream()) 2618 if (isStream())
2618 { 2619 {
2619 - QTC::TC("qpdf", "QPDFObjectHandle ERR clone stream"); 2620 + QTC::TC("qpdf", "QPDFObjectHandle copy stream",
  2621 + stop_at_streams ? 0 : 1);
  2622 + if (stop_at_streams)
  2623 + {
  2624 + return;
  2625 + }
2620 throw std::runtime_error( 2626 throw std::runtime_error(
2621 "attempt to make a stream into a direct object"); 2627 "attempt to make a stream into a direct object");
2622 } 2628 }
@@ -2690,7 +2696,8 @@ QPDFObjectHandle::copyObject(std::set&lt;QPDFObjGen&gt;&amp; visited, @@ -2690,7 +2696,8 @@ QPDFObjectHandle::copyObject(std::set&lt;QPDFObjGen&gt;&amp; visited,
2690 (cross_indirect || (! items.back().isIndirect()))) 2696 (cross_indirect || (! items.back().isIndirect())))
2691 { 2697 {
2692 items.back().copyObject( 2698 items.back().copyObject(
2693 - visited, cross_indirect, first_level_only); 2699 + visited, cross_indirect,
  2700 + first_level_only, stop_at_streams);
2694 } 2701 }
2695 } 2702 }
2696 new_obj = new QPDF_Array(items); 2703 new_obj = new QPDF_Array(items);
@@ -2708,7 +2715,8 @@ QPDFObjectHandle::copyObject(std::set&lt;QPDFObjGen&gt;&amp; visited, @@ -2708,7 +2715,8 @@ QPDFObjectHandle::copyObject(std::set&lt;QPDFObjGen&gt;&amp; visited,
2708 (cross_indirect || (! items[*iter].isIndirect()))) 2715 (cross_indirect || (! items[*iter].isIndirect())))
2709 { 2716 {
2710 items[*iter].copyObject( 2717 items[*iter].copyObject(
2711 - visited, cross_indirect, first_level_only); 2718 + visited, cross_indirect,
  2719 + first_level_only, stop_at_streams);
2712 } 2720 }
2713 } 2721 }
2714 new_obj = new QPDF_Dictionary(items); 2722 new_obj = new QPDF_Dictionary(items);
@@ -2730,8 +2738,14 @@ QPDFObjectHandle::copyObject(std::set&lt;QPDFObjGen&gt;&amp; visited, @@ -2730,8 +2738,14 @@ QPDFObjectHandle::copyObject(std::set&lt;QPDFObjGen&gt;&amp; visited,
2730 void 2738 void
2731 QPDFObjectHandle::makeDirect() 2739 QPDFObjectHandle::makeDirect()
2732 { 2740 {
  2741 + makeDirect(false);
  2742 +}
  2743 +
  2744 +void
  2745 +QPDFObjectHandle::makeDirect(bool allow_streams)
  2746 +{
2733 std::set<QPDFObjGen> visited; 2747 std::set<QPDFObjGen> visited;
2734 - copyObject(visited, true, false); 2748 + copyObject(visited, true, false, allow_streams);
2735 } 2749 }
2736 2750
2737 void 2751 void
qpdf/qpdf.testcov
@@ -79,7 +79,7 @@ QPDFObjectHandle clone string 0 @@ -79,7 +79,7 @@ QPDFObjectHandle clone string 0
79 QPDFObjectHandle clone array 0 79 QPDFObjectHandle clone array 0
80 QPDFObjectHandle clone dictionary 0 80 QPDFObjectHandle clone dictionary 0
81 QPDFObjectHandle makeDirect loop 0 81 QPDFObjectHandle makeDirect loop 0
82 -QPDFObjectHandle ERR clone stream 0 82 +QPDFObjectHandle copy stream 1
83 QPDF default for xref stream field 0 0 83 QPDF default for xref stream field 0 0
84 QPDF prev key in xref stream dictionary 0 84 QPDF prev key in xref stream dictionary 0
85 QPDF prev key in trailer dictionary 0 85 QPDF prev key in trailer dictionary 0
qpdf/qtest/qpdf.test
@@ -2963,7 +2963,7 @@ $td-&gt;runtest(&quot;check output&quot;, @@ -2963,7 +2963,7 @@ $td-&gt;runtest(&quot;check output&quot;,
2963 show_ntests(); 2963 show_ntests();
2964 # ---------- 2964 # ----------
2965 $td->notify("--- Mutability Tests ---"); 2965 $td->notify("--- Mutability Tests ---");
2966 -$n_tests += 4; 2966 +$n_tests += 5;
2967 2967
2968 $td->runtest("no normalization", 2968 $td->runtest("no normalization",
2969 {$td->COMMAND => "test_driver 4 test4-1.pdf"}, 2969 {$td->COMMAND => "test_driver 4 test4-1.pdf"},
@@ -2975,13 +2975,18 @@ $td-&gt;runtest(&quot;object ordering&quot;, @@ -2975,13 +2975,18 @@ $td-&gt;runtest(&quot;object ordering&quot;,
2975 {$td->FILE => "test4-4.qdf", 2975 {$td->FILE => "test4-4.qdf",
2976 $td->EXIT_STATUS => 0}); 2976 $td->EXIT_STATUS => 0});
2977 2977
2978 -$td->runtest("loop detected", 2978 +$td->runtest("make direct with allow_streams",
  2979 + {$td->COMMAND => "test_driver 4 test4-5.pdf"},
  2980 + {$td->FILE => "test4-5.qdf",
  2981 + $td->EXIT_STATUS => 0});
  2982 +
  2983 +$td->runtest("stream detected",
2979 {$td->COMMAND => "test_driver 4 test4-2.pdf"}, 2984 {$td->COMMAND => "test_driver 4 test4-2.pdf"},
2980 {$td->FILE => "test4-2.out", 2985 {$td->FILE => "test4-2.out",
2981 $td->EXIT_STATUS => 2}, 2986 $td->EXIT_STATUS => 2},
2982 $td->NORMALIZE_NEWLINES); 2987 $td->NORMALIZE_NEWLINES);
2983 2988
2984 -$td->runtest("stream detected", 2989 +$td->runtest("loop detected",
2985 {$td->COMMAND => "test_driver 4 test4-3.pdf"}, 2990 {$td->COMMAND => "test_driver 4 test4-3.pdf"},
2986 {$td->FILE => "test4-3.out", 2991 {$td->FILE => "test4-3.out",
2987 $td->EXIT_STATUS => 2}, 2992 $td->EXIT_STATUS => 2},
qpdf/qtest/qpdf/test4-5.pdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +1 0 obj
  6 +<<
  7 + /Pages 3 0 R
  8 + /Type /Catalog
  9 +>>
  10 +endobj
  11 +
  12 +2 0 obj
  13 +<<
  14 + /A 4 0 R
  15 + /B 5 0 R
  16 + /Subject (Subject)
  17 + /Title (Some Title Is Here)
  18 +>>
  19 +endobj
  20 +
  21 +3 0 obj
  22 +<<
  23 + /Count 1
  24 + /Kids [
  25 + 6 0 R
  26 + ]
  27 + /Type /Pages
  28 +>>
  29 +endobj
  30 +
  31 +4 0 obj
  32 +[
  33 + 100
  34 + 2
  35 + 3
  36 +]
  37 +endobj
  38 +
  39 +5 0 obj
  40 +<<
  41 + /A 4 0 R
  42 + /B (B)
  43 +>>
  44 +endobj
  45 +
  46 +%% Page 1
  47 +6 0 obj
  48 +<<
  49 + /Contents 7 0 R
  50 + /MediaBox [
  51 + 0
  52 + 0
  53 + 612
  54 + 792
  55 + ]
  56 + /Parent 3 0 R
  57 + /Resources <<
  58 + /Font <<
  59 + /F1 9 0 R
  60 + >>
  61 + /ProcSet 10 0 R
  62 + >>
  63 + /Type /Page
  64 +>>
  65 +endobj
  66 +
  67 +%% Contents for page 1
  68 +7 0 obj
  69 +<<
  70 + /Length 8 0 R
  71 +>>
  72 +stream
  73 +BT
  74 + /F1 24 Tf
  75 + 72 720 Td
  76 + (Potato) Tj
  77 +ET
  78 +endstream
  79 +endobj
  80 +
  81 +8 0 obj
  82 +44
  83 +endobj
  84 +
  85 +9 0 obj
  86 +<<
  87 + /BaseFont /Helvetica
  88 + /Encoding /WinAnsiEncoding
  89 + /Name /F1
  90 + /Subtype /Type1
  91 + /Type /Font
  92 +>>
  93 +endobj
  94 +
  95 +10 0 obj
  96 +[
  97 + /PDF
  98 + /Text
  99 +]
  100 +endobj
  101 +
  102 +11 0 obj
  103 +<<
  104 + /A 12 0 R
  105 + /C (potato)
  106 +>>
  107 +endobj
  108 +
  109 +12 0 obj
  110 +<< /B 13 0 R >>
  111 +endobj
  112 +
  113 +13 0 obj
  114 +<<
  115 + /Length 14 0 R
  116 +>>
  117 +stream
  118 +salad
  119 +endstream
  120 +endobj
  121 +
  122 +14 0 obj
  123 +6
  124 +endobj
  125 +
  126 +xref
  127 +0 15
  128 +0000000000 65535 f
  129 +0000000025 00000 n
  130 +0000000079 00000 n
  131 +0000000174 00000 n
  132 +0000000246 00000 n
  133 +0000000280 00000 n
  134 +0000000332 00000 n
  135 +0000000548 00000 n
  136 +0000000647 00000 n
  137 +0000000666 00000 n
  138 +0000000784 00000 n
  139 +0000000820 00000 n
  140 +0000000869 00000 n
  141 +0000000902 00000 n
  142 +0000000965 00000 n
  143 +trailer <<
  144 + /QTest 2 0 R
  145 + /QTest2 11 0 R
  146 + /Root 1 0 R
  147 + /Size 15
  148 + /ID [<c61bd35bada064f61e0a56aa9588064e><c893e7330be149468080ad6518819868>]
  149 +>>
  150 +startxref
  151 +984
  152 +%%EOF
qpdf/qtest/qpdf/test4-5.qdf 0 โ†’ 100644
  1 +%PDF-1.3
  2 +%ยฟรทยขรพ
  3 +%QDF-1.0
  4 +
  5 +%% Original object ID: 1 0
  6 +1 0 obj
  7 +<<
  8 + /Pages 6 0 R
  9 + /Type /Catalog
  10 +>>
  11 +endobj
  12 +
  13 +%% Original object ID: 15 0
  14 +2 0 obj
  15 +<<
  16 + /A [
  17 + 14
  18 + 15
  19 + 9
  20 + ]
  21 + /Author (Mr. Potato Head)
  22 + /B <<
  23 + /A [
  24 + 100
  25 + 2
  26 + 3
  27 + ]
  28 + /B (B)
  29 + >>
  30 + /Title (Some Title Is Here)
  31 +>>
  32 +endobj
  33 +
  34 +%% Original object ID: 2 0
  35 +3 0 obj
  36 +<<
  37 + /A 7 0 R
  38 + /B 8 0 R
  39 + /Subject (Subject)
  40 + /Title (Some Title Is Here)
  41 +>>
  42 +endobj
  43 +
  44 +%% Original object ID: 13 0
  45 +4 0 obj
  46 +<<
  47 + /Length 5 0 R
  48 +>>
  49 +stream
  50 +salad
  51 +endstream
  52 +endobj
  53 +
  54 +5 0 obj
  55 +6
  56 +endobj
  57 +
  58 +%% Original object ID: 3 0
  59 +6 0 obj
  60 +<<
  61 + /Count 1
  62 + /Kids [
  63 + 9 0 R
  64 + ]
  65 + /Type /Pages
  66 +>>
  67 +endobj
  68 +
  69 +%% Original object ID: 4 0
  70 +7 0 obj
  71 +[
  72 + 100
  73 + 2
  74 + 3
  75 +]
  76 +endobj
  77 +
  78 +%% Original object ID: 5 0
  79 +8 0 obj
  80 +<<
  81 + /A 7 0 R
  82 + /B (B)
  83 +>>
  84 +endobj
  85 +
  86 +%% Page 1
  87 +%% Original object ID: 6 0
  88 +9 0 obj
  89 +<<
  90 + /Contents 10 0 R
  91 + /MediaBox [
  92 + 0
  93 + 0
  94 + 612
  95 + 792
  96 + ]
  97 + /Parent 6 0 R
  98 + /Resources <<
  99 + /Font <<
  100 + /F1 12 0 R
  101 + >>
  102 + /ProcSet 13 0 R
  103 + >>
  104 + /Type /Page
  105 +>>
  106 +endobj
  107 +
  108 +%% Contents for page 1
  109 +%% Original object ID: 7 0
  110 +10 0 obj
  111 +<<
  112 + /Length 11 0 R
  113 +>>
  114 +stream
  115 +BT
  116 + /F1 24 Tf
  117 + 72 720 Td
  118 + (Potato) Tj
  119 +ET
  120 +endstream
  121 +endobj
  122 +
  123 +11 0 obj
  124 +44
  125 +endobj
  126 +
  127 +%% Original object ID: 9 0
  128 +12 0 obj
  129 +<<
  130 + /BaseFont /Helvetica
  131 + /Encoding /WinAnsiEncoding
  132 + /Name /F1
  133 + /Subtype /Type1
  134 + /Type /Font
  135 +>>
  136 +endobj
  137 +
  138 +%% Original object ID: 10 0
  139 +13 0 obj
  140 +[
  141 + /PDF
  142 + /Text
  143 +]
  144 +endobj
  145 +
  146 +xref
  147 +0 14
  148 +0000000000 65535 f
  149 +0000000052 00000 n
  150 +0000000134 00000 n
  151 +0000000337 00000 n
  152 +0000000460 00000 n
  153 +0000000521 00000 n
  154 +0000000566 00000 n
  155 +0000000665 00000 n
  156 +0000000726 00000 n
  157 +0000000805 00000 n
  158 +0000001050 00000 n
  159 +0000001151 00000 n
  160 +0000001198 00000 n
  161 +0000001345 00000 n
  162 +trailer <<
  163 + /Info 2 0 R
  164 + /QTest 3 0 R
  165 + /QTest2 <<
  166 + /A <<
  167 + /B 4 0 R
  168 + >>
  169 + /C (potato)
  170 + >>
  171 + /Root 1 0 R
  172 + /Size 14
  173 + /ID [<c61bd35bada064f61e0a56aa9588064e><31415926535897932384626433832795>]
  174 +>>
  175 +startxref
  176 +1381
  177 +%%EOF
qpdf/test_driver.cc
@@ -485,6 +485,14 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -485,6 +485,14 @@ void runtest(int n, char const* filename1, char const* arg2)
485 A.setArrayFromVector(items); 485 A.setArrayFromVector(items);
486 } 486 }
487 487
  488 + QPDFObjectHandle qtest2 = trailer.getKey("/QTest2");
  489 + if (! qtest2.isNull())
  490 + {
  491 + // Test allow_streams=true
  492 + qtest2.makeDirect(true);
  493 + trailer.replaceKey("/QTest2", qtest2);
  494 + }
  495 +
488 trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest)); 496 trailer.replaceKey("/Info", pdf.makeIndirectObject(qtest));
489 QPDFWriter w(pdf, 0); 497 QPDFWriter w(pdf, 0);
490 w.setQDFMode(true); 498 w.setQDFMode(true);