Commit b5614f611d3057359dfd7ba63418c62787af5511
1 parent
04edfe9f
Implement repair and insert for name/number trees
Showing
19 changed files
with
2059 additions
and
77 deletions
ChangeLog
| 1 | 2021-01-23 Jay Berkenbilt <ejb@ql.org> | 1 | 2021-01-23 Jay Berkenbilt <ejb@ql.org> |
| 2 | 2 | ||
| 3 | + * Add an insert method to QPDFNameTreeObjectHelper and | ||
| 4 | + QPDFNumberTreeObjectHelper. | ||
| 5 | + | ||
| 6 | + * QPDFNameTreeObjectHelper and QPDFNumberTreeObjectHelper will | ||
| 7 | + automatically repair broken name and number trees by default. This | ||
| 8 | + behavior can be turned off. | ||
| 9 | + | ||
| 3 | * Change behavior of QPDFObjectHandle::newUnicodeString so that it | 10 | * Change behavior of QPDFObjectHandle::newUnicodeString so that it |
| 4 | encodes ASCII or PDFDocEncoding if those encodings will support | 11 | encodes ASCII or PDFDocEncoding if those encodings will support |
| 5 | all the characters in the string, resorting to UTF-16 only if the | 12 | all the characters in the string, resorting to UTF-16 only if the |
TODO
| @@ -261,8 +261,6 @@ I find it useful to make reference to them in this list. | @@ -261,8 +261,6 @@ I find it useful to make reference to them in this list. | ||
| 261 | dictionary may need to be changed -- create test cases with lots of | 261 | dictionary may need to be changed -- create test cases with lots of |
| 262 | duplicated/overlapping keys. | 262 | duplicated/overlapping keys. |
| 263 | 263 | ||
| 264 | - * Add support for writing name and number trees | ||
| 265 | - | ||
| 266 | * Figure out how to render Gajić correctly in the PDF version of the | 264 | * Figure out how to render Gajić correctly in the PDF version of the |
| 267 | qpdf manual. | 265 | qpdf manual. |
| 268 | 266 |
include/qpdf/QPDFNameTreeObjectHelper.hh
| @@ -127,12 +127,21 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper | @@ -127,12 +127,21 @@ class QPDFNameTreeObjectHelper: public QPDFObjectHelper | ||
| 127 | iterator find(std::string const& key, | 127 | iterator find(std::string const& key, |
| 128 | bool return_prev_if_not_found = false); | 128 | bool return_prev_if_not_found = false); |
| 129 | 129 | ||
| 130 | + // Insert a new item. If the key already exists, it is replaced. | ||
| 131 | + QPDF_DLL | ||
| 132 | + iterator insert(std::string const& key, QPDFObjectHandle value); | ||
| 133 | + | ||
| 130 | // Return the contents of the name tree as a map. Note that name | 134 | // Return the contents of the name tree as a map. Note that name |
| 131 | // trees may be very large, so this may use a lot of RAM. It is | 135 | // trees may be very large, so this may use a lot of RAM. It is |
| 132 | // more efficient to use QPDFNameTreeObjectHelper's iterator. | 136 | // more efficient to use QPDFNameTreeObjectHelper's iterator. |
| 133 | QPDF_DLL | 137 | QPDF_DLL |
| 134 | std::map<std::string, QPDFObjectHandle> getAsMap() const; | 138 | std::map<std::string, QPDFObjectHandle> getAsMap() const; |
| 135 | 139 | ||
| 140 | + // Split a node if the number of items exceeds this value. There's | ||
| 141 | + // no real reason to ever set this except for testing. | ||
| 142 | + QPDF_DLL | ||
| 143 | + void setSplitThreshold(int); | ||
| 144 | + | ||
| 136 | private: | 145 | private: |
| 137 | class Members | 146 | class Members |
| 138 | { | 147 | { |
include/qpdf/QPDFNumberTreeObjectHelper.hh
| @@ -145,6 +145,10 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper | @@ -145,6 +145,10 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper | ||
| 145 | QPDF_DLL | 145 | QPDF_DLL |
| 146 | iterator find(numtree_number key, bool return_prev_if_not_found = false); | 146 | iterator find(numtree_number key, bool return_prev_if_not_found = false); |
| 147 | 147 | ||
| 148 | + // Insert a new item. If the key already exists, it is replaced. | ||
| 149 | + QPDF_DLL | ||
| 150 | + iterator insert(numtree_number key, QPDFObjectHandle value); | ||
| 151 | + | ||
| 148 | // Return the contents of the number tree as a map. Note that | 152 | // Return the contents of the number tree as a map. Note that |
| 149 | // number trees may be very large, so this may use a lot of RAM. | 153 | // number trees may be very large, so this may use a lot of RAM. |
| 150 | // It is more efficient to use QPDFNumberTreeObjectHelper's | 154 | // It is more efficient to use QPDFNumberTreeObjectHelper's |
| @@ -153,6 +157,11 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper | @@ -153,6 +157,11 @@ class QPDFNumberTreeObjectHelper: public QPDFObjectHelper | ||
| 153 | QPDF_DLL | 157 | QPDF_DLL |
| 154 | idx_map getAsMap() const; | 158 | idx_map getAsMap() const; |
| 155 | 159 | ||
| 160 | + // Split a node if the number of items exceeds this value. There's | ||
| 161 | + // no real reason to ever set this except for testing. | ||
| 162 | + QPDF_DLL | ||
| 163 | + void setSplitThreshold(int); | ||
| 164 | + | ||
| 156 | private: | 165 | private: |
| 157 | class Members | 166 | class Members |
| 158 | { | 167 | { |
libqpdf/NNTree.cc
| @@ -44,6 +44,12 @@ error(QPDF* qpdf, QPDFObjectHandle& node, std::string const& msg) | @@ -44,6 +44,12 @@ error(QPDF* qpdf, QPDFObjectHandle& node, std::string const& msg) | ||
| 44 | } | 44 | } |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | +NNTreeIterator::NNTreeIterator(NNTreeImpl& impl) : | ||
| 48 | + impl(impl), | ||
| 49 | + item_number(-1) | ||
| 50 | +{ | ||
| 51 | +} | ||
| 52 | + | ||
| 47 | NNTreeIterator::PathElement::PathElement( | 53 | NNTreeIterator::PathElement::PathElement( |
| 48 | QPDFObjectHandle const& node, int kid_number) : | 54 | QPDFObjectHandle const& node, int kid_number) : |
| 49 | node(node), | 55 | node(node), |
| @@ -52,18 +58,36 @@ NNTreeIterator::PathElement::PathElement( | @@ -52,18 +58,36 @@ NNTreeIterator::PathElement::PathElement( | ||
| 52 | } | 58 | } |
| 53 | 59 | ||
| 54 | QPDFObjectHandle | 60 | QPDFObjectHandle |
| 55 | -NNTreeIterator::PathElement::getNextKid(bool backward) | 61 | +NNTreeIterator::getNextKid(PathElement& pe, bool backward) |
| 56 | { | 62 | { |
| 57 | - kid_number += backward ? -1 : 1; | ||
| 58 | - auto kids = node.getKey("/Kids"); | ||
| 59 | QPDFObjectHandle result; | 63 | QPDFObjectHandle result; |
| 60 | - if ((kid_number >= 0) && (kid_number < kids.getArrayNItems())) | ||
| 61 | - { | ||
| 62 | - result = kids.getArrayItem(kid_number); | ||
| 63 | - } | ||
| 64 | - else | 64 | + bool found = false; |
| 65 | + while (! found) | ||
| 65 | { | 66 | { |
| 66 | - result = QPDFObjectHandle::newNull(); | 67 | + pe.kid_number += backward ? -1 : 1; |
| 68 | + auto kids = pe.node.getKey("/Kids"); | ||
| 69 | + if ((pe.kid_number >= 0) && (pe.kid_number < kids.getArrayNItems())) | ||
| 70 | + { | ||
| 71 | + result = kids.getArrayItem(pe.kid_number); | ||
| 72 | + if (result.isDictionary() && | ||
| 73 | + (result.hasKey("/Kids") || | ||
| 74 | + result.hasKey(impl.details.itemsKey()))) | ||
| 75 | + { | ||
| 76 | + found = true; | ||
| 77 | + } | ||
| 78 | + else | ||
| 79 | + { | ||
| 80 | + QTC::TC("qpdf", "NNTree skip invalid kid"); | ||
| 81 | + warn(impl.qpdf, pe.node, | ||
| 82 | + "skipping over invalid kid at index " + | ||
| 83 | + QUtil::int_to_string(pe.kid_number)); | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + else | ||
| 87 | + { | ||
| 88 | + result = QPDFObjectHandle::newNull(); | ||
| 89 | + found = true; | ||
| 90 | + } | ||
| 67 | } | 91 | } |
| 68 | return result; | 92 | return result; |
| 69 | } | 93 | } |
| @@ -83,30 +107,358 @@ NNTreeIterator::increment(bool backward) | @@ -83,30 +107,358 @@ NNTreeIterator::increment(bool backward) | ||
| 83 | "attempt made to increment or decrement an invalid" | 107 | "attempt made to increment or decrement an invalid" |
| 84 | " name/number tree iterator"); | 108 | " name/number tree iterator"); |
| 85 | } | 109 | } |
| 86 | - this->item_number += backward ? -2 : 2; | ||
| 87 | - auto items = this->node.getKey(details.itemsKey()); | ||
| 88 | - if ((this->item_number < 0) || | ||
| 89 | - (this->item_number >= items.getArrayNItems())) | 110 | + bool found_valid_key = false; |
| 111 | + while (valid() && (! found_valid_key)) | ||
| 90 | { | 112 | { |
| 91 | - bool found = false; | ||
| 92 | - setItemNumber(QPDFObjectHandle(), -1); | ||
| 93 | - while (! (found || this->path.empty())) | 113 | + this->item_number += backward ? -2 : 2; |
| 114 | + auto items = this->node.getKey(impl.details.itemsKey()); | ||
| 115 | + if ((this->item_number < 0) || | ||
| 116 | + (this->item_number >= items.getArrayNItems())) | ||
| 117 | + { | ||
| 118 | + bool found = false; | ||
| 119 | + setItemNumber(QPDFObjectHandle(), -1); | ||
| 120 | + while (! (found || this->path.empty())) | ||
| 121 | + { | ||
| 122 | + auto& element = this->path.back(); | ||
| 123 | + auto pe_node = getNextKid(element, backward); | ||
| 124 | + if (pe_node.isNull()) | ||
| 125 | + { | ||
| 126 | + this->path.pop_back(); | ||
| 127 | + } | ||
| 128 | + else | ||
| 129 | + { | ||
| 130 | + found = deepen(pe_node, ! backward, false); | ||
| 131 | + } | ||
| 132 | + } | ||
| 133 | + } | ||
| 134 | + if (this->item_number >= 0) | ||
| 94 | { | 135 | { |
| 95 | - auto& element = this->path.back(); | ||
| 96 | - auto node = element.getNextKid(backward); | ||
| 97 | - if (node.isNull()) | 136 | + items = this->node.getKey(impl.details.itemsKey()); |
| 137 | + if (this->item_number + 1 >= items.getArrayNItems()) | ||
| 98 | { | 138 | { |
| 99 | - this->path.pop_back(); | 139 | + QTC::TC("qpdf", "NNTree skip item at end of short items"); |
| 140 | + warn(impl.qpdf, this->node, | ||
| 141 | + "items array doesn't have enough elements"); | ||
| 142 | + } | ||
| 143 | + else if (! impl.details.keyValid( | ||
| 144 | + items.getArrayItem(this->item_number))) | ||
| 145 | + { | ||
| 146 | + QTC::TC("qpdf", "NNTree skip invalid key"); | ||
| 147 | + warn(impl.qpdf, this->node, | ||
| 148 | + "item " + QUtil::int_to_string(this->item_number) + | ||
| 149 | + " has the wrong type"); | ||
| 100 | } | 150 | } |
| 101 | else | 151 | else |
| 102 | { | 152 | { |
| 103 | - deepen(node, ! backward); | ||
| 104 | - found = true; | 153 | + found_valid_key = true; |
| 154 | + } | ||
| 155 | + } | ||
| 156 | + } | ||
| 157 | +} | ||
| 158 | + | ||
| 159 | +void | ||
| 160 | +NNTreeIterator::resetLimits(QPDFObjectHandle node, | ||
| 161 | + std::list<PathElement>::iterator parent) | ||
| 162 | +{ | ||
| 163 | + bool done = false; | ||
| 164 | + while (! done) | ||
| 165 | + { | ||
| 166 | + auto kids = node.getKey("/Kids"); | ||
| 167 | + int nkids = kids.isArray() ? kids.getArrayNItems() : 0; | ||
| 168 | + auto items = node.getKey(impl.details.itemsKey()); | ||
| 169 | + int nitems = items.isArray() ? items.getArrayNItems() : 0; | ||
| 170 | + | ||
| 171 | + bool changed = true; | ||
| 172 | + QPDFObjectHandle first; | ||
| 173 | + QPDFObjectHandle last; | ||
| 174 | + if (nitems >= 2) | ||
| 175 | + { | ||
| 176 | + first = items.getArrayItem(0); | ||
| 177 | + last = items.getArrayItem((nitems - 1) & ~1); | ||
| 178 | + } | ||
| 179 | + else if (nkids > 0) | ||
| 180 | + { | ||
| 181 | + auto first_kid = kids.getArrayItem(0); | ||
| 182 | + auto last_kid = kids.getArrayItem(nkids - 1); | ||
| 183 | + if (first_kid.isDictionary() && last_kid.isDictionary()) | ||
| 184 | + { | ||
| 185 | + auto first_limits = first_kid.getKey("/Limits"); | ||
| 186 | + auto last_limits = last_kid.getKey("/Limits"); | ||
| 187 | + if (first_limits.isArray() && | ||
| 188 | + (first_limits.getArrayNItems() >= 2) && | ||
| 189 | + last_limits.isArray() && | ||
| 190 | + (last_limits.getArrayNItems() >= 2)) | ||
| 191 | + { | ||
| 192 | + first = first_limits.getArrayItem(0); | ||
| 193 | + last = last_limits.getArrayItem(1); | ||
| 194 | + } | ||
| 105 | } | 195 | } |
| 106 | } | 196 | } |
| 197 | + if (first.isInitialized() && last.isInitialized()) | ||
| 198 | + { | ||
| 199 | + auto limits = QPDFObjectHandle::newArray(); | ||
| 200 | + limits.appendItem(first); | ||
| 201 | + limits.appendItem(last); | ||
| 202 | + auto olimits = node.getKey("/Limits"); | ||
| 203 | + if (olimits.isArray() && (olimits.getArrayNItems() == 2)) | ||
| 204 | + { | ||
| 205 | + auto ofirst = olimits.getArrayItem(0); | ||
| 206 | + auto olast = olimits.getArrayItem(1); | ||
| 207 | + if (impl.details.keyValid(ofirst) && | ||
| 208 | + impl.details.keyValid(olast) && | ||
| 209 | + (impl.details.compareKeys(first, ofirst) == 0) && | ||
| 210 | + (impl.details.compareKeys(last, olast) == 0)) | ||
| 211 | + { | ||
| 212 | + QTC::TC("qpdf", "NNTree limits didn't change"); | ||
| 213 | + changed = false; | ||
| 214 | + } | ||
| 215 | + } | ||
| 216 | + if (changed) | ||
| 217 | + { | ||
| 218 | + node.replaceKey("/Limits", limits); | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | + else | ||
| 222 | + { | ||
| 223 | + QTC::TC("qpdf", "NNTree unable to determine limits"); | ||
| 224 | + warn(impl.qpdf, node, "unable to determine limits"); | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + if ((! changed) || (parent == this->path.begin())) | ||
| 228 | + { | ||
| 229 | + done = true; | ||
| 230 | + } | ||
| 231 | + else | ||
| 232 | + { | ||
| 233 | + node = parent->node; | ||
| 234 | + --parent; | ||
| 235 | + } | ||
| 107 | } | 236 | } |
| 108 | } | 237 | } |
| 109 | 238 | ||
| 239 | +void | ||
| 240 | +NNTreeIterator::split(QPDFObjectHandle to_split, | ||
| 241 | + std::list<PathElement>::iterator parent) | ||
| 242 | +{ | ||
| 243 | + // Split some node along the path to the item pointed to by this | ||
| 244 | + // iterator, and adjust the iterator so it points to the same | ||
| 245 | + // item. | ||
| 246 | + | ||
| 247 | + // In examples, for simplicity, /Nums is show to just contain | ||
| 248 | + // numbers instead of pairs. Imagine this tre: | ||
| 249 | + // | ||
| 250 | + // root: << /Kids [ A B C D ] >> | ||
| 251 | + // A: << /Nums [ 1 2 3 4 ] >> | ||
| 252 | + // B: << /Nums [ 5 6 7 8 ] >> | ||
| 253 | + // C: << /Nums [ 9 10 11 12 ] >> | ||
| 254 | + // D: << /Kids [ E F ] | ||
| 255 | + // E: << /Nums [ 13 14 15 16 ] >> | ||
| 256 | + // F: << /Nums [ 17 18 19 20 ] >> | ||
| 257 | + | ||
| 258 | + // iter1 (points to 19) | ||
| 259 | + // path: | ||
| 260 | + // - { node: root: kid_number: 3 } | ||
| 261 | + // - { node: D, kid_number: 1 } | ||
| 262 | + // node: F | ||
| 263 | + // item_number: 2 | ||
| 264 | + | ||
| 265 | + // iter2 (points to 1) | ||
| 266 | + // path: | ||
| 267 | + // - { node: root, kid_number: 0} | ||
| 268 | + // node: A | ||
| 269 | + // item_number: 0 | ||
| 270 | + | ||
| 271 | + if (! this->impl.qpdf) | ||
| 272 | + { | ||
| 273 | + throw std::logic_error( | ||
| 274 | + "NNTreeIterator::split called with null qpdf"); | ||
| 275 | + } | ||
| 276 | + if (! valid()) | ||
| 277 | + { | ||
| 278 | + throw std::logic_error( | ||
| 279 | + "NNTreeIterator::split called an invalid iterator"); | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + // Find the array we actually need to split, which is either this | ||
| 283 | + // node's kids or items. | ||
| 284 | + auto kids = to_split.getKey("/Kids"); | ||
| 285 | + int nkids = kids.isArray() ? kids.getArrayNItems() : 0; | ||
| 286 | + auto items = to_split.getKey(impl.details.itemsKey()); | ||
| 287 | + int nitems = items.isArray() ? items.getArrayNItems() : 0; | ||
| 288 | + | ||
| 289 | + QPDFObjectHandle first_half; | ||
| 290 | + int n = 0; | ||
| 291 | + std::string key; | ||
| 292 | + int threshold = 0; | ||
| 293 | + if (nkids > 0) | ||
| 294 | + { | ||
| 295 | + QTC::TC("qpdf", "NNTree split kids"); | ||
| 296 | + first_half = kids; | ||
| 297 | + n = nkids; | ||
| 298 | + threshold = impl.split_threshold; | ||
| 299 | + key = "/Kids"; | ||
| 300 | + } | ||
| 301 | + else if (nitems > 0) | ||
| 302 | + { | ||
| 303 | + QTC::TC("qpdf", "NNTree split items"); | ||
| 304 | + first_half = items; | ||
| 305 | + n = nitems; | ||
| 306 | + threshold = 2 * impl.split_threshold; | ||
| 307 | + key = impl.details.itemsKey(); | ||
| 308 | + } | ||
| 309 | + else | ||
| 310 | + { | ||
| 311 | + throw std::logic_error("NNTreeIterator::split called on invalid node"); | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + if (n <= threshold) | ||
| 315 | + { | ||
| 316 | + return; | ||
| 317 | + } | ||
| 318 | + | ||
| 319 | + bool is_root = (parent == this->path.end()); | ||
| 320 | + bool is_leaf = (nitems > 0); | ||
| 321 | + | ||
| 322 | + // CURRENT STATE: tree is in original state; iterator is valid and | ||
| 323 | + // unchanged. | ||
| 324 | + | ||
| 325 | + if (is_root) | ||
| 326 | + { | ||
| 327 | + // What we want to do is to create a new node for the second | ||
| 328 | + // half of the items and put it in the parent's /Kids array | ||
| 329 | + // right after the element that points to the current to_split | ||
| 330 | + // node, but if we're splitting root, there is no parent, so | ||
| 331 | + // handle that first. | ||
| 332 | + | ||
| 333 | + // In the non-root case, parent points to the path element | ||
| 334 | + // whose /Kids contains the first half node, and the first | ||
| 335 | + // half node is to_split. If we are splitting the root, we | ||
| 336 | + // need to push everything down a level, but we want to keep | ||
| 337 | + // the actual root object the same so that indirect references | ||
| 338 | + // to it remain intact (and also in case it might be a direct | ||
| 339 | + // object, which it shouldn't be but that case probably exists | ||
| 340 | + // in the wild). To achieve this, we create a new node for the | ||
| 341 | + // first half and then replace /Kids in the root to contain | ||
| 342 | + // it. Then we adjust the path so that the first element is | ||
| 343 | + // root and the second element, if any, is the new first half. | ||
| 344 | + // In this way, we make the root case identical to the | ||
| 345 | + // non-root case so remaining logic can handle them in the | ||
| 346 | + // same way. | ||
| 347 | + | ||
| 348 | + auto first_node = impl.qpdf->makeIndirectObject( | ||
| 349 | + QPDFObjectHandle::newDictionary()); | ||
| 350 | + first_node.replaceKey(key, first_half); | ||
| 351 | + QPDFObjectHandle new_kids = QPDFObjectHandle::newArray(); | ||
| 352 | + new_kids.appendItem(first_node); | ||
| 353 | + to_split.removeKey("/Limits"); // already shouldn't be there for root | ||
| 354 | + to_split.removeKey(impl.details.itemsKey()); | ||
| 355 | + to_split.replaceKey("/Kids", new_kids); | ||
| 356 | + if (is_leaf) | ||
| 357 | + { | ||
| 358 | + QTC::TC("qpdf", "NNTree split root + leaf"); | ||
| 359 | + this->node = first_node; | ||
| 360 | + } | ||
| 361 | + else | ||
| 362 | + { | ||
| 363 | + QTC::TC("qpdf", "NNTree split root + !leaf"); | ||
| 364 | + auto next = this->path.begin(); | ||
| 365 | + next->node = first_node; | ||
| 366 | + } | ||
| 367 | + this->path.push_front(PathElement(to_split, 0)); | ||
| 368 | + parent = this->path.begin(); | ||
| 369 | + to_split = first_node; | ||
| 370 | + } | ||
| 371 | + | ||
| 372 | + // CURRENT STATE: parent is guaranteed to be defined, and we have | ||
| 373 | + // the invariants that parent[/Kids][kid_number] == to_split and | ||
| 374 | + // (++parent).node == to_split. | ||
| 375 | + | ||
| 376 | + // Create a second half array, and transfer the second half of the | ||
| 377 | + // items into the second half array. | ||
| 378 | + QPDFObjectHandle second_half = QPDFObjectHandle::newArray(); | ||
| 379 | + int start_idx = ((n / 2) & ~1); | ||
| 380 | + while (first_half.getArrayNItems() > start_idx) | ||
| 381 | + { | ||
| 382 | + second_half.appendItem(first_half.getArrayItem(start_idx)); | ||
| 383 | + first_half.eraseItem(start_idx); | ||
| 384 | + } | ||
| 385 | + resetLimits(to_split, parent); | ||
| 386 | + | ||
| 387 | + // Create a new node to contain the second half | ||
| 388 | + QPDFObjectHandle second_node = impl.qpdf->makeIndirectObject( | ||
| 389 | + QPDFObjectHandle::newDictionary()); | ||
| 390 | + second_node.replaceKey(key, second_half); | ||
| 391 | + resetLimits(second_node, parent); | ||
| 392 | + | ||
| 393 | + // CURRENT STATE: half the items from the kids or items array in | ||
| 394 | + // the node being split have been moved into a new node. The new | ||
| 395 | + // node is not yet attached to the tree. The iterator have a path | ||
| 396 | + // element or leaf node that is out of bounds. | ||
| 397 | + | ||
| 398 | + // We need to adjust the parent to add the second node to /Kids | ||
| 399 | + // and, if needed, update kid_number to traverse through it. We | ||
| 400 | + // need to update to_split's path element, or the node if this is | ||
| 401 | + // a leaf, so that the kid/item number points to the right place. | ||
| 402 | + | ||
| 403 | + auto parent_kids = parent->node.getKey("/Kids"); | ||
| 404 | + parent_kids.insertItem(parent->kid_number + 1, second_node); | ||
| 405 | + auto cur_elem = parent; | ||
| 406 | + ++cur_elem; // points to end() for leaf nodes | ||
| 407 | + int old_idx = (is_leaf ? this->item_number : cur_elem->kid_number); | ||
| 408 | + if (old_idx >= start_idx) | ||
| 409 | + { | ||
| 410 | + ++parent->kid_number; | ||
| 411 | + if (is_leaf) | ||
| 412 | + { | ||
| 413 | + QTC::TC("qpdf", "NNTree split second half item"); | ||
| 414 | + setItemNumber(second_node, this->item_number - start_idx); | ||
| 415 | + } | ||
| 416 | + else | ||
| 417 | + { | ||
| 418 | + QTC::TC("qpdf", "NNTree split second half kid"); | ||
| 419 | + cur_elem->node = second_node; | ||
| 420 | + cur_elem->kid_number -= start_idx; | ||
| 421 | + } | ||
| 422 | + } | ||
| 423 | + if (! is_root) | ||
| 424 | + { | ||
| 425 | + QTC::TC("qpdf", "NNTree split parent"); | ||
| 426 | + auto next = parent->node; | ||
| 427 | + resetLimits(next, parent); | ||
| 428 | + --parent; | ||
| 429 | + split(next, parent); | ||
| 430 | + } | ||
| 431 | +} | ||
| 432 | + | ||
| 433 | +std::list<NNTreeIterator::PathElement>::iterator | ||
| 434 | +NNTreeIterator::lastPathElement() | ||
| 435 | +{ | ||
| 436 | + auto result = this->path.end(); | ||
| 437 | + if (! this->path.empty()) | ||
| 438 | + { | ||
| 439 | + --result; | ||
| 440 | + } | ||
| 441 | + return result; | ||
| 442 | +} | ||
| 443 | + | ||
| 444 | +void | ||
| 445 | +NNTreeIterator::insertAfter(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 446 | +{ | ||
| 447 | + auto items = this->node.getKey(impl.details.itemsKey()); | ||
| 448 | + if (! items.isArray()) | ||
| 449 | + { | ||
| 450 | + error(impl.qpdf, node, "node contains no items array"); | ||
| 451 | + } | ||
| 452 | + if (items.getArrayNItems() < this->item_number + 2) | ||
| 453 | + { | ||
| 454 | + error(impl.qpdf, node, "items array is too short"); | ||
| 455 | + } | ||
| 456 | + items.insertItem(this->item_number + 2, key); | ||
| 457 | + items.insertItem(this->item_number + 3, value); | ||
| 458 | + resetLimits(this->node, lastPathElement()); | ||
| 459 | + split(this->node, lastPathElement()); | ||
| 460 | +} | ||
| 461 | + | ||
| 110 | NNTreeIterator& | 462 | NNTreeIterator& |
| 111 | NNTreeIterator::operator++() | 463 | NNTreeIterator::operator++() |
| 112 | { | 464 | { |
| @@ -130,7 +482,11 @@ NNTreeIterator::operator*() | @@ -130,7 +482,11 @@ NNTreeIterator::operator*() | ||
| 130 | "attempt made to dereference an invalid" | 482 | "attempt made to dereference an invalid" |
| 131 | " name/number tree iterator"); | 483 | " name/number tree iterator"); |
| 132 | } | 484 | } |
| 133 | - auto items = this->node.getKey(details.itemsKey()); | 485 | + auto items = this->node.getKey(impl.details.itemsKey()); |
| 486 | + if (items.getArrayNItems() < this->item_number + 2) | ||
| 487 | + { | ||
| 488 | + error(impl.qpdf, node, "items array is too short"); | ||
| 489 | + } | ||
| 134 | return std::make_pair(items.getArrayItem(this->item_number), | 490 | return std::make_pair(items.getArrayItem(this->item_number), |
| 135 | items.getArrayItem(1+this->item_number)); | 491 | items.getArrayItem(1+this->item_number)); |
| 136 | } | 492 | } |
| @@ -178,18 +534,18 @@ NNTreeIterator::addPathElement(QPDFObjectHandle const& node, | @@ -178,18 +534,18 @@ NNTreeIterator::addPathElement(QPDFObjectHandle const& node, | ||
| 178 | this->path.push_back(PathElement(node, kid_number)); | 534 | this->path.push_back(PathElement(node, kid_number)); |
| 179 | } | 535 | } |
| 180 | 536 | ||
| 181 | -void | ||
| 182 | -NNTreeIterator::reset() | 537 | +bool |
| 538 | +NNTreeIterator::deepen(QPDFObjectHandle node, bool first, bool allow_empty) | ||
| 183 | { | 539 | { |
| 184 | - this->path.clear(); | ||
| 185 | - this->item_number = -1; | ||
| 186 | -} | 540 | + // Starting at this node, descend through the first or last kid |
| 541 | + // until we reach a node with items. If we succeed, return true; | ||
| 542 | + // otherwise return false and leave path alone. | ||
| 543 | + | ||
| 544 | + auto opath = this->path; | ||
| 545 | + bool failed = false; | ||
| 187 | 546 | ||
| 188 | -void | ||
| 189 | -NNTreeIterator::deepen(QPDFObjectHandle node, bool first) | ||
| 190 | -{ | ||
| 191 | std::set<QPDFObjGen> seen; | 547 | std::set<QPDFObjGen> seen; |
| 192 | - while (true) | 548 | + while (! failed) |
| 193 | { | 549 | { |
| 194 | if (node.isIndirect()) | 550 | if (node.isIndirect()) |
| 195 | { | 551 | { |
| @@ -197,16 +553,25 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first) | @@ -197,16 +553,25 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first) | ||
| 197 | if (seen.count(og)) | 553 | if (seen.count(og)) |
| 198 | { | 554 | { |
| 199 | QTC::TC("qpdf", "NNTree deepen: loop"); | 555 | QTC::TC("qpdf", "NNTree deepen: loop"); |
| 200 | - warn(qpdf, node, | 556 | + warn(impl.qpdf, node, |
| 201 | "loop detected while traversing name/number tree"); | 557 | "loop detected while traversing name/number tree"); |
| 202 | - reset(); | ||
| 203 | - return; | 558 | + failed = true; |
| 559 | + break; | ||
| 204 | } | 560 | } |
| 205 | seen.insert(og); | 561 | seen.insert(og); |
| 206 | } | 562 | } |
| 563 | + if (! node.isDictionary()) | ||
| 564 | + { | ||
| 565 | + QTC::TC("qpdf", "NNTree node is not a dictionary"); | ||
| 566 | + warn(impl.qpdf, node, | ||
| 567 | + "non-dictionary node while traversing name/number tree"); | ||
| 568 | + failed = true; | ||
| 569 | + break; | ||
| 570 | + } | ||
| 571 | + | ||
| 207 | auto kids = node.getKey("/Kids"); | 572 | auto kids = node.getKey("/Kids"); |
| 208 | int nkids = kids.isArray() ? kids.getArrayNItems() : 0; | 573 | int nkids = kids.isArray() ? kids.getArrayNItems() : 0; |
| 209 | - auto items = node.getKey(details.itemsKey()); | 574 | + auto items = node.getKey(impl.details.itemsKey()); |
| 210 | int nitems = items.isArray() ? items.getArrayNItems() : 0; | 575 | int nitems = items.isArray() ? items.getArrayNItems() : 0; |
| 211 | if (nitems > 0) | 576 | if (nitems > 0) |
| 212 | { | 577 | { |
| @@ -217,17 +582,51 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first) | @@ -217,17 +582,51 @@ NNTreeIterator::deepen(QPDFObjectHandle node, bool first) | ||
| 217 | { | 582 | { |
| 218 | int kid_number = first ? 0 : nkids - 1; | 583 | int kid_number = first ? 0 : nkids - 1; |
| 219 | addPathElement(node, kid_number); | 584 | addPathElement(node, kid_number); |
| 220 | - node = kids.getArrayItem(kid_number); | 585 | + auto next = kids.getArrayItem(kid_number); |
| 586 | + if (! next.isIndirect()) | ||
| 587 | + { | ||
| 588 | + if (impl.qpdf && impl.auto_repair) | ||
| 589 | + { | ||
| 590 | + QTC::TC("qpdf", "NNTree fix indirect kid"); | ||
| 591 | + warn(impl.qpdf, node, | ||
| 592 | + "converting kid number " + | ||
| 593 | + QUtil::int_to_string(kid_number) + | ||
| 594 | + " to an indirect object"); | ||
| 595 | + next = impl.qpdf->makeIndirectObject(next); | ||
| 596 | + kids.setArrayItem(kid_number, next); | ||
| 597 | + } | ||
| 598 | + else | ||
| 599 | + { | ||
| 600 | + QTC::TC("qpdf", "NNTree warn indirect kid"); | ||
| 601 | + warn(impl.qpdf, node, | ||
| 602 | + "kid number " + QUtil::int_to_string(kid_number) + | ||
| 603 | + " is not an indirect object"); | ||
| 604 | + } | ||
| 605 | + } | ||
| 606 | + node = next; | ||
| 607 | + } | ||
| 608 | + else if (allow_empty && items.isArray()) | ||
| 609 | + { | ||
| 610 | + QTC::TC("qpdf", "NNTree deepen found empty"); | ||
| 611 | + setItemNumber(node, -1); | ||
| 612 | + break; | ||
| 221 | } | 613 | } |
| 222 | else | 614 | else |
| 223 | { | 615 | { |
| 224 | QTC::TC("qpdf", "NNTree deepen: invalid node"); | 616 | QTC::TC("qpdf", "NNTree deepen: invalid node"); |
| 225 | - warn(qpdf, node, | ||
| 226 | - "name/number tree node has neither /Kids nor /Names"); | ||
| 227 | - reset(); | ||
| 228 | - return; | 617 | + warn(impl.qpdf, node, |
| 618 | + "name/number tree node has neither non-empty " + | ||
| 619 | + impl.details.itemsKey() + " nor /Kids"); | ||
| 620 | + failed = true; | ||
| 621 | + break; | ||
| 229 | } | 622 | } |
| 230 | } | 623 | } |
| 624 | + if (failed) | ||
| 625 | + { | ||
| 626 | + this->path = opath; | ||
| 627 | + return false; | ||
| 628 | + } | ||
| 629 | + return true; | ||
| 231 | } | 630 | } |
| 232 | 631 | ||
| 233 | NNTreeImpl::NNTreeImpl(NNTreeDetails const& details, | 632 | NNTreeImpl::NNTreeImpl(NNTreeDetails const& details, |
| @@ -236,29 +635,37 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const& details, | @@ -236,29 +635,37 @@ NNTreeImpl::NNTreeImpl(NNTreeDetails const& details, | ||
| 236 | bool auto_repair) : | 635 | bool auto_repair) : |
| 237 | details(details), | 636 | details(details), |
| 238 | qpdf(qpdf), | 637 | qpdf(qpdf), |
| 239 | - oh(oh) | 638 | + split_threshold(32), |
| 639 | + oh(oh), | ||
| 640 | + auto_repair(auto_repair) | ||
| 240 | { | 641 | { |
| 241 | } | 642 | } |
| 242 | 643 | ||
| 644 | +void | ||
| 645 | +NNTreeImpl::setSplitThreshold(int split_threshold) | ||
| 646 | +{ | ||
| 647 | + this->split_threshold = split_threshold; | ||
| 648 | +} | ||
| 649 | + | ||
| 243 | NNTreeImpl::iterator | 650 | NNTreeImpl::iterator |
| 244 | NNTreeImpl::begin() | 651 | NNTreeImpl::begin() |
| 245 | { | 652 | { |
| 246 | - iterator result(details, this->qpdf); | ||
| 247 | - result.deepen(this->oh, true); | 653 | + iterator result(*this); |
| 654 | + result.deepen(this->oh, true, true); | ||
| 248 | return result; | 655 | return result; |
| 249 | } | 656 | } |
| 250 | 657 | ||
| 251 | NNTreeImpl::iterator | 658 | NNTreeImpl::iterator |
| 252 | NNTreeImpl::end() | 659 | NNTreeImpl::end() |
| 253 | { | 660 | { |
| 254 | - return iterator(details, this->qpdf); | 661 | + return iterator(*this); |
| 255 | } | 662 | } |
| 256 | 663 | ||
| 257 | NNTreeImpl::iterator | 664 | NNTreeImpl::iterator |
| 258 | NNTreeImpl::last() | 665 | NNTreeImpl::last() |
| 259 | { | 666 | { |
| 260 | - iterator result(details, this->qpdf); | ||
| 261 | - result.deepen(this->oh, false); | 667 | + iterator result(*this); |
| 668 | + result.deepen(this->oh, false, true); | ||
| 262 | return result; | 669 | return result; |
| 263 | } | 670 | } |
| 264 | 671 | ||
| @@ -282,9 +689,8 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node) | @@ -282,9 +689,8 @@ NNTreeImpl::withinLimits(QPDFObjectHandle key, QPDFObjectHandle node) | ||
| 282 | } | 689 | } |
| 283 | else | 690 | else |
| 284 | { | 691 | { |
| 285 | - // The root node has no limits, so consider the item to be in | ||
| 286 | - // here if there are no limits. This will cause checking lower | ||
| 287 | - // items. | 692 | + QTC::TC("qpdf", "NNTree missing limits"); |
| 693 | + error(qpdf, node, "node is missing /Limits"); | ||
| 288 | } | 694 | } |
| 289 | return result; | 695 | return result; |
| 290 | } | 696 | } |
| @@ -294,7 +700,7 @@ NNTreeImpl::binarySearch( | @@ -294,7 +700,7 @@ NNTreeImpl::binarySearch( | ||
| 294 | QPDFObjectHandle key, QPDFObjectHandle items, | 700 | QPDFObjectHandle key, QPDFObjectHandle items, |
| 295 | int num_items, bool return_prev_if_not_found, | 701 | int num_items, bool return_prev_if_not_found, |
| 296 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, | 702 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, |
| 297 | - QPDFObjectHandle& node, | 703 | + QPDFObjectHandle& arr, |
| 298 | int item)) | 704 | int item)) |
| 299 | { | 705 | { |
| 300 | int max_idx = 1; | 706 | int max_idx = 1; |
| @@ -372,6 +778,7 @@ NNTreeImpl::compareKeyItem( | @@ -372,6 +778,7 @@ NNTreeImpl::compareKeyItem( | ||
| 372 | if (! ((items.isArray() && (items.getArrayNItems() > (2 * idx)) && | 778 | if (! ((items.isArray() && (items.getArrayNItems() > (2 * idx)) && |
| 373 | details.keyValid(items.getArrayItem(2 * idx))))) | 779 | details.keyValid(items.getArrayItem(2 * idx))))) |
| 374 | { | 780 | { |
| 781 | + QTC::TC("qpdf", "NNTree item is wrong type"); | ||
| 375 | error(qpdf, this->oh, | 782 | error(qpdf, this->oh, |
| 376 | "item at index " + QUtil::int_to_string(2 * idx) + | 783 | "item at index " + QUtil::int_to_string(2 * idx) + |
| 377 | " is not the right type"); | 784 | " is not the right type"); |
| @@ -386,6 +793,7 @@ NNTreeImpl::compareKeyKid( | @@ -386,6 +793,7 @@ NNTreeImpl::compareKeyKid( | ||
| 386 | if (! (kids.isArray() && (idx < kids.getArrayNItems()) && | 793 | if (! (kids.isArray() && (idx < kids.getArrayNItems()) && |
| 387 | kids.getArrayItem(idx).isDictionary())) | 794 | kids.getArrayItem(idx).isDictionary())) |
| 388 | { | 795 | { |
| 796 | + QTC::TC("qpdf", "NNTree kid is invalid"); | ||
| 389 | error(qpdf, this->oh, | 797 | error(qpdf, this->oh, |
| 390 | "invalid kid at index " + QUtil::int_to_string(idx)); | 798 | "invalid kid at index " + QUtil::int_to_string(idx)); |
| 391 | } | 799 | } |
| @@ -393,12 +801,56 @@ NNTreeImpl::compareKeyKid( | @@ -393,12 +801,56 @@ NNTreeImpl::compareKeyKid( | ||
| 393 | } | 801 | } |
| 394 | 802 | ||
| 395 | 803 | ||
| 804 | +void | ||
| 805 | +NNTreeImpl::repair() | ||
| 806 | +{ | ||
| 807 | + auto new_node = QPDFObjectHandle::newDictionary(); | ||
| 808 | + new_node.replaceKey(details.itemsKey(), QPDFObjectHandle::newArray()); | ||
| 809 | + NNTreeImpl repl(details, qpdf, new_node, false); | ||
| 810 | + for (auto i: *this) | ||
| 811 | + { | ||
| 812 | + repl.insert(i.first, i.second); | ||
| 813 | + } | ||
| 814 | + this->oh.replaceKey("/Kids", new_node.getKey("/Kids")); | ||
| 815 | + this->oh.replaceKey( | ||
| 816 | + details.itemsKey(), new_node.getKey(details.itemsKey())); | ||
| 817 | +} | ||
| 818 | + | ||
| 396 | NNTreeImpl::iterator | 819 | NNTreeImpl::iterator |
| 397 | NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | 820 | NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) |
| 398 | { | 821 | { |
| 822 | + try | ||
| 823 | + { | ||
| 824 | + return findInternal(key, return_prev_if_not_found); | ||
| 825 | + } | ||
| 826 | + catch (QPDFExc& e) | ||
| 827 | + { | ||
| 828 | + if (this->auto_repair) | ||
| 829 | + { | ||
| 830 | + QTC::TC("qpdf", "NNTree repair"); | ||
| 831 | + warn(qpdf, this->oh, | ||
| 832 | + std::string("attempting to repair after error: ") + e.what()); | ||
| 833 | + repair(); | ||
| 834 | + return findInternal(key, return_prev_if_not_found); | ||
| 835 | + } | ||
| 836 | + else | ||
| 837 | + { | ||
| 838 | + throw e; | ||
| 839 | + } | ||
| 840 | + } | ||
| 841 | +} | ||
| 842 | + | ||
| 843 | +NNTreeImpl::iterator | ||
| 844 | +NNTreeImpl::findInternal(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 845 | +{ | ||
| 399 | auto first_item = begin(); | 846 | auto first_item = begin(); |
| 400 | auto last_item = end(); | 847 | auto last_item = end(); |
| 401 | - if (first_item.valid() && | 848 | + if (first_item == end()) |
| 849 | + { | ||
| 850 | + // Empty | ||
| 851 | + return end(); | ||
| 852 | + } | ||
| 853 | + else if (first_item.valid() && | ||
| 402 | details.keyValid((*first_item).first) && | 854 | details.keyValid((*first_item).first) && |
| 403 | details.compareKeys(key, (*first_item).first) < 0) | 855 | details.compareKeys(key, (*first_item).first) < 0) |
| 404 | { | 856 | { |
| @@ -422,13 +874,14 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -422,13 +874,14 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 422 | 874 | ||
| 423 | std::set<QPDFObjGen> seen; | 875 | std::set<QPDFObjGen> seen; |
| 424 | auto node = this->oh; | 876 | auto node = this->oh; |
| 425 | - iterator result(details, this->qpdf); | 877 | + iterator result(*this); |
| 426 | 878 | ||
| 427 | while (true) | 879 | while (true) |
| 428 | { | 880 | { |
| 429 | auto og = node.getObjGen(); | 881 | auto og = node.getObjGen(); |
| 430 | if (seen.count(og)) | 882 | if (seen.count(og)) |
| 431 | { | 883 | { |
| 884 | + QTC::TC("qpdf", "NNTree loop in find"); | ||
| 432 | error(qpdf, node, "loop detected in find"); | 885 | error(qpdf, node, "loop detected in find"); |
| 433 | } | 886 | } |
| 434 | seen.insert(og); | 887 | seen.insert(og); |
| @@ -455,18 +908,67 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | @@ -455,18 +908,67 @@ NNTreeImpl::find(QPDFObjectHandle key, bool return_prev_if_not_found) | ||
| 455 | &NNTreeImpl::compareKeyKid); | 908 | &NNTreeImpl::compareKeyKid); |
| 456 | if (idx == -1) | 909 | if (idx == -1) |
| 457 | { | 910 | { |
| 911 | + QTC::TC("qpdf", "NNTree -1 in binary search"); | ||
| 458 | error(qpdf, node, | 912 | error(qpdf, node, |
| 459 | "unexpected -1 from binary search of kids;" | 913 | "unexpected -1 from binary search of kids;" |
| 460 | - " tree may not be sorted"); | 914 | + " limits may by wrong"); |
| 461 | } | 915 | } |
| 462 | result.addPathElement(node, idx); | 916 | result.addPathElement(node, idx); |
| 463 | node = kids.getArrayItem(idx); | 917 | node = kids.getArrayItem(idx); |
| 464 | } | 918 | } |
| 465 | else | 919 | else |
| 466 | { | 920 | { |
| 921 | + QTC::TC("qpdf", "NNTree bad node during find"); | ||
| 467 | error(qpdf, node, "bad node during find"); | 922 | error(qpdf, node, "bad node during find"); |
| 468 | } | 923 | } |
| 469 | } | 924 | } |
| 470 | 925 | ||
| 471 | return result; | 926 | return result; |
| 472 | } | 927 | } |
| 928 | + | ||
| 929 | +NNTreeImpl::iterator | ||
| 930 | +NNTreeImpl::insertFirst(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 931 | +{ | ||
| 932 | + auto iter = begin(); | ||
| 933 | + QPDFObjectHandle items; | ||
| 934 | + if (iter.node.isInitialized() && | ||
| 935 | + iter.node.isDictionary()) | ||
| 936 | + { | ||
| 937 | + items = iter.node.getKey(details.itemsKey()); | ||
| 938 | + } | ||
| 939 | + if (! (items.isInitialized() && items.isArray())) | ||
| 940 | + { | ||
| 941 | + QTC::TC("qpdf", "NNTree no valid items node in insertFirst"); | ||
| 942 | + error(qpdf, this->oh, "unable to find a valid items node"); | ||
| 943 | + } | ||
| 944 | + items.insertItem(0, key); | ||
| 945 | + items.insertItem(1, value); | ||
| 946 | + iter.item_number = 0; | ||
| 947 | + iter.resetLimits(iter.node, iter.lastPathElement()); | ||
| 948 | + iter.split(iter.node, iter.lastPathElement()); | ||
| 949 | + return begin(); | ||
| 950 | +} | ||
| 951 | + | ||
| 952 | +NNTreeImpl::iterator | ||
| 953 | +NNTreeImpl::insert(QPDFObjectHandle key, QPDFObjectHandle value) | ||
| 954 | +{ | ||
| 955 | + auto iter = find(key, true); | ||
| 956 | + if (! iter.valid()) | ||
| 957 | + { | ||
| 958 | + QTC::TC("qpdf", "NNTree insert inserts first"); | ||
| 959 | + return insertFirst(key, value); | ||
| 960 | + } | ||
| 961 | + else if (details.compareKeys(key, (*iter).first) == 0) | ||
| 962 | + { | ||
| 963 | + QTC::TC("qpdf", "NNTree insert replaces"); | ||
| 964 | + auto items = iter.node.getKey(details.itemsKey()); | ||
| 965 | + items.setArrayItem(iter.item_number + 1, value); | ||
| 966 | + } | ||
| 967 | + else | ||
| 968 | + { | ||
| 969 | + QTC::TC("qpdf", "NNTree insert inserts after"); | ||
| 970 | + iter.insertAfter(key, value); | ||
| 971 | + ++iter; | ||
| 972 | + } | ||
| 973 | + return iter; | ||
| 974 | +} |
libqpdf/QPDFNameTreeObjectHelper.cc
| @@ -122,6 +122,15 @@ QPDFNameTreeObjectHelper::find(std::string const& key, | @@ -122,6 +122,15 @@ QPDFNameTreeObjectHelper::find(std::string const& key, | ||
| 122 | return iterator(std::make_shared<NNTreeIterator>(i)); | 122 | return iterator(std::make_shared<NNTreeIterator>(i)); |
| 123 | } | 123 | } |
| 124 | 124 | ||
| 125 | +QPDFNameTreeObjectHelper::iterator | ||
| 126 | +QPDFNameTreeObjectHelper::insert(std::string const& key, | ||
| 127 | + QPDFObjectHandle value) | ||
| 128 | +{ | ||
| 129 | + auto i = this->m->impl->insert( | ||
| 130 | + QPDFObjectHandle::newUnicodeString(key), value); | ||
| 131 | + return iterator(std::make_shared<NNTreeIterator>(i)); | ||
| 132 | +} | ||
| 133 | + | ||
| 125 | bool | 134 | bool |
| 126 | QPDFNameTreeObjectHelper::hasName(std::string const& name) | 135 | QPDFNameTreeObjectHelper::hasName(std::string const& name) |
| 127 | { | 136 | { |
| @@ -142,6 +151,12 @@ QPDFNameTreeObjectHelper::findObject( | @@ -142,6 +151,12 @@ QPDFNameTreeObjectHelper::findObject( | ||
| 142 | return true; | 151 | return true; |
| 143 | } | 152 | } |
| 144 | 153 | ||
| 154 | +void | ||
| 155 | +QPDFNameTreeObjectHelper::setSplitThreshold(int t) | ||
| 156 | +{ | ||
| 157 | + this->m->impl->setSplitThreshold(t); | ||
| 158 | +} | ||
| 159 | + | ||
| 145 | std::map<std::string, QPDFObjectHandle> | 160 | std::map<std::string, QPDFObjectHandle> |
| 146 | QPDFNameTreeObjectHelper::getAsMap() const | 161 | QPDFNameTreeObjectHelper::getAsMap() const |
| 147 | { | 162 | { |
libqpdf/QPDFNumberTreeObjectHelper.cc
| @@ -118,6 +118,14 @@ QPDFNumberTreeObjectHelper::find(numtree_number key, | @@ -118,6 +118,14 @@ QPDFNumberTreeObjectHelper::find(numtree_number key, | ||
| 118 | return iterator(std::make_shared<NNTreeIterator>(i)); | 118 | return iterator(std::make_shared<NNTreeIterator>(i)); |
| 119 | } | 119 | } |
| 120 | 120 | ||
| 121 | +QPDFNumberTreeObjectHelper::iterator | ||
| 122 | +QPDFNumberTreeObjectHelper::insert(numtree_number key, QPDFObjectHandle value) | ||
| 123 | +{ | ||
| 124 | + auto i = this->m->impl->insert( | ||
| 125 | + QPDFObjectHandle::newInteger(key), value); | ||
| 126 | + return iterator(std::make_shared<NNTreeIterator>(i)); | ||
| 127 | +} | ||
| 128 | + | ||
| 121 | QPDFNumberTreeObjectHelper::numtree_number | 129 | QPDFNumberTreeObjectHelper::numtree_number |
| 122 | QPDFNumberTreeObjectHelper::getMin() | 130 | QPDFNumberTreeObjectHelper::getMin() |
| 123 | { | 131 | { |
| @@ -175,6 +183,12 @@ QPDFNumberTreeObjectHelper::findObjectAtOrBelow( | @@ -175,6 +183,12 @@ QPDFNumberTreeObjectHelper::findObjectAtOrBelow( | ||
| 175 | return true; | 183 | return true; |
| 176 | } | 184 | } |
| 177 | 185 | ||
| 186 | +void | ||
| 187 | +QPDFNumberTreeObjectHelper::setSplitThreshold(int t) | ||
| 188 | +{ | ||
| 189 | + this->m->impl->setSplitThreshold(t); | ||
| 190 | +} | ||
| 191 | + | ||
| 178 | std::map<QPDFNumberTreeObjectHelper::numtree_number, QPDFObjectHandle> | 192 | std::map<QPDFNumberTreeObjectHelper::numtree_number, QPDFObjectHandle> |
| 179 | QPDFNumberTreeObjectHelper::getAsMap() const | 193 | QPDFNumberTreeObjectHelper::getAsMap() const |
| 180 | { | 194 | { |
libqpdf/qpdf/NNTree.hh
| @@ -15,6 +15,7 @@ class NNTreeDetails | @@ -15,6 +15,7 @@ class NNTreeDetails | ||
| 15 | virtual int compareKeys(QPDFObjectHandle, QPDFObjectHandle) const = 0; | 15 | virtual int compareKeys(QPDFObjectHandle, QPDFObjectHandle) const = 0; |
| 16 | }; | 16 | }; |
| 17 | 17 | ||
| 18 | +class NNTreeImpl; | ||
| 18 | class NNTreeIterator: public std::iterator< | 19 | class NNTreeIterator: public std::iterator< |
| 19 | std::bidirectional_iterator_tag, | 20 | std::bidirectional_iterator_tag, |
| 20 | std::pair<QPDFObjectHandle, QPDFObjectHandle>, | 21 | std::pair<QPDFObjectHandle, QPDFObjectHandle>, |
| @@ -46,32 +47,34 @@ class NNTreeIterator: public std::iterator< | @@ -46,32 +47,34 @@ class NNTreeIterator: public std::iterator< | ||
| 46 | return ! operator==(other); | 47 | return ! operator==(other); |
| 47 | } | 48 | } |
| 48 | 49 | ||
| 50 | + void insertAfter( | ||
| 51 | + QPDFObjectHandle key, QPDFObjectHandle value); | ||
| 52 | + | ||
| 49 | private: | 53 | private: |
| 50 | class PathElement | 54 | class PathElement |
| 51 | { | 55 | { |
| 52 | public: | 56 | public: |
| 53 | PathElement(QPDFObjectHandle const& node, int kid_number); | 57 | PathElement(QPDFObjectHandle const& node, int kid_number); |
| 54 | - QPDFObjectHandle getNextKid(bool backward); | ||
| 55 | 58 | ||
| 56 | QPDFObjectHandle node; | 59 | QPDFObjectHandle node; |
| 57 | int kid_number; | 60 | int kid_number; |
| 58 | }; | 61 | }; |
| 59 | 62 | ||
| 60 | // ABI: for qpdf 11, make qpdf a reference | 63 | // ABI: for qpdf 11, make qpdf a reference |
| 61 | - NNTreeIterator(NNTreeDetails const& details, QPDF* qpdf) : | ||
| 62 | - details(details), | ||
| 63 | - qpdf(qpdf), | ||
| 64 | - item_number(-1) | ||
| 65 | - { | ||
| 66 | - } | ||
| 67 | - void reset(); | ||
| 68 | - void deepen(QPDFObjectHandle node, bool first); | 64 | + NNTreeIterator(NNTreeImpl& impl); |
| 65 | + bool deepen(QPDFObjectHandle node, bool first, bool allow_empty); | ||
| 69 | void setItemNumber(QPDFObjectHandle const& node, int); | 66 | void setItemNumber(QPDFObjectHandle const& node, int); |
| 70 | void addPathElement(QPDFObjectHandle const& node, int kid_number); | 67 | void addPathElement(QPDFObjectHandle const& node, int kid_number); |
| 68 | + QPDFObjectHandle getNextKid(PathElement& element, bool backward); | ||
| 71 | void increment(bool backward); | 69 | void increment(bool backward); |
| 70 | + void resetLimits(QPDFObjectHandle node, | ||
| 71 | + std::list<PathElement>::iterator parent); | ||
| 72 | 72 | ||
| 73 | - NNTreeDetails const& details; | ||
| 74 | - QPDF* qpdf; | 73 | + void split(QPDFObjectHandle to_split, |
| 74 | + std::list<PathElement>::iterator parent); | ||
| 75 | + std::list<PathElement>::iterator lastPathElement(); | ||
| 76 | + | ||
| 77 | + NNTreeImpl& impl; | ||
| 75 | std::list<PathElement> path; | 78 | std::list<PathElement> path; |
| 76 | QPDFObjectHandle node; | 79 | QPDFObjectHandle node; |
| 77 | int item_number; | 80 | int item_number; |
| @@ -79,6 +82,7 @@ class NNTreeIterator: public std::iterator< | @@ -79,6 +82,7 @@ class NNTreeIterator: public std::iterator< | ||
| 79 | 82 | ||
| 80 | class NNTreeImpl | 83 | class NNTreeImpl |
| 81 | { | 84 | { |
| 85 | + friend class NNTreeIterator; | ||
| 82 | public: | 86 | public: |
| 83 | typedef NNTreeIterator iterator; | 87 | typedef NNTreeIterator iterator; |
| 84 | 88 | ||
| @@ -88,14 +92,24 @@ class NNTreeImpl | @@ -88,14 +92,24 @@ class NNTreeImpl | ||
| 88 | iterator end(); | 92 | iterator end(); |
| 89 | iterator last(); | 93 | iterator last(); |
| 90 | iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false); | 94 | iterator find(QPDFObjectHandle key, bool return_prev_if_not_found = false); |
| 95 | + iterator insertFirst(QPDFObjectHandle key, QPDFObjectHandle value); | ||
| 96 | + iterator insert(QPDFObjectHandle key, QPDFObjectHandle value); | ||
| 97 | + | ||
| 98 | + // Change the split threshold for easier testing. There's no real | ||
| 99 | + // reason to expose this to downstream tree helpers, but it has to | ||
| 100 | + // be public so we can call it from the test suite. | ||
| 101 | + void setSplitThreshold(int split_threshold); | ||
| 91 | 102 | ||
| 92 | private: | 103 | private: |
| 104 | + void repair(); | ||
| 105 | + iterator findInternal( | ||
| 106 | + QPDFObjectHandle key, bool return_prev_if_not_found = false); | ||
| 93 | int withinLimits(QPDFObjectHandle key, QPDFObjectHandle node); | 107 | int withinLimits(QPDFObjectHandle key, QPDFObjectHandle node); |
| 94 | int binarySearch( | 108 | int binarySearch( |
| 95 | QPDFObjectHandle key, QPDFObjectHandle items, | 109 | QPDFObjectHandle key, QPDFObjectHandle items, |
| 96 | int num_items, bool return_prev_if_not_found, | 110 | int num_items, bool return_prev_if_not_found, |
| 97 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, | 111 | int (NNTreeImpl::*compare)(QPDFObjectHandle& key, |
| 98 | - QPDFObjectHandle& node, | 112 | + QPDFObjectHandle& arr, |
| 99 | int item)); | 113 | int item)); |
| 100 | int compareKeyItem( | 114 | int compareKeyItem( |
| 101 | QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); | 115 | QPDFObjectHandle& key, QPDFObjectHandle& items, int idx); |
| @@ -104,7 +118,9 @@ class NNTreeImpl | @@ -104,7 +118,9 @@ class NNTreeImpl | ||
| 104 | 118 | ||
| 105 | NNTreeDetails const& details; | 119 | NNTreeDetails const& details; |
| 106 | QPDF* qpdf; | 120 | QPDF* qpdf; |
| 121 | + int split_threshold; | ||
| 107 | QPDFObjectHandle oh; | 122 | QPDFObjectHandle oh; |
| 123 | + bool auto_repair; | ||
| 108 | }; | 124 | }; |
| 109 | 125 | ||
| 110 | #endif // NNTREE_HH | 126 | #endif // NNTREE_HH |
manual/qpdf-manual.xml
| @@ -4857,7 +4857,9 @@ print "\n"; | @@ -4857,7 +4857,9 @@ print "\n"; | ||
| 4857 | <para> | 4857 | <para> |
| 4858 | Re-implement <classname>QPDFNameTreeObjectHelper</classname> | 4858 | Re-implement <classname>QPDFNameTreeObjectHelper</classname> |
| 4859 | and <classname>QPDFNumberTreeObjectHelper</classname> to be | 4859 | and <classname>QPDFNumberTreeObjectHelper</classname> to be |
| 4860 | - more efficient, and add an iterator-based API. | 4860 | + more efficient, add an iterator-based API, give them the |
| 4861 | + capability to repair broken trees, and create methods for | ||
| 4862 | + modifying the trees. | ||
| 4861 | </para> | 4863 | </para> |
| 4862 | </listitem> | 4864 | </listitem> |
| 4863 | </itemizedlist> | 4865 | </itemizedlist> |
qpdf/qpdf.testcov
| @@ -524,3 +524,30 @@ QPDFWriter getFilterOnWrite false 0 | @@ -524,3 +524,30 @@ QPDFWriter getFilterOnWrite false 0 | ||
| 524 | QPDFPageObjectHelper::forEachXObject 3 | 524 | QPDFPageObjectHelper::forEachXObject 3 |
| 525 | NNTree deepen: invalid node 0 | 525 | NNTree deepen: invalid node 0 |
| 526 | NNTree deepen: loop 0 | 526 | NNTree deepen: loop 0 |
| 527 | +NNTree skip invalid kid 0 | ||
| 528 | +NNTree skip item at end of short items 0 | ||
| 529 | +NNTree skip invalid key 0 | ||
| 530 | +NNTree no valid items node in insertFirst 0 | ||
| 531 | +NNTree deepen found empty 0 | ||
| 532 | +NNTree insert inserts first 0 | ||
| 533 | +NNTree insert replaces 0 | ||
| 534 | +NNTree insert inserts after 0 | ||
| 535 | +NNTree unable to determine limits 0 | ||
| 536 | +NNTree warn indirect kid 0 | ||
| 537 | +NNTree fix indirect kid 0 | ||
| 538 | +NNTree repair 0 | ||
| 539 | +NNTree split root + leaf 0 | ||
| 540 | +NNTree split root + !leaf 0 | ||
| 541 | +NNTree split kids 0 | ||
| 542 | +NNTree split items 0 | ||
| 543 | +NNTree split second half item 0 | ||
| 544 | +NNTree split parent 0 | ||
| 545 | +NNTree split second half kid 0 | ||
| 546 | +NNTree missing limits 0 | ||
| 547 | +NNTree item is wrong type 0 | ||
| 548 | +NNTree kid is invalid 0 | ||
| 549 | +NNTree loop in find 0 | ||
| 550 | +NNTree -1 in binary search 0 | ||
| 551 | +NNTree bad node during find 0 | ||
| 552 | +NNTree node is not a dictionary 0 | ||
| 553 | +NNTree limits didn't change 0 |
qpdf/qtest/qpdf.test
| @@ -583,7 +583,7 @@ foreach my $input (@ext_inputs) | @@ -583,7 +583,7 @@ foreach my $input (@ext_inputs) | ||
| 583 | show_ntests(); | 583 | show_ntests(); |
| 584 | # ---------- | 584 | # ---------- |
| 585 | $td->notify("--- Number and Name Trees ---"); | 585 | $td->notify("--- Number and Name Trees ---"); |
| 586 | -$n_tests += 2; | 586 | +$n_tests += 4; |
| 587 | 587 | ||
| 588 | $td->runtest("number trees", | 588 | $td->runtest("number trees", |
| 589 | {$td->COMMAND => "test_driver 46 number-tree.pdf"}, | 589 | {$td->COMMAND => "test_driver 46 number-tree.pdf"}, |
| @@ -593,6 +593,13 @@ $td->runtest("name trees", | @@ -593,6 +593,13 @@ $td->runtest("name trees", | ||
| 593 | {$td->COMMAND => "test_driver 48 name-tree.pdf"}, | 593 | {$td->COMMAND => "test_driver 48 name-tree.pdf"}, |
| 594 | {$td->FILE => "name-tree.out", $td->EXIT_STATUS => 0}, | 594 | {$td->FILE => "name-tree.out", $td->EXIT_STATUS => 0}, |
| 595 | $td->NORMALIZE_NEWLINES); | 595 | $td->NORMALIZE_NEWLINES); |
| 596 | +$td->runtest("nntree split", | ||
| 597 | + {$td->COMMAND => "test_driver 74 split-nntree.pdf"}, | ||
| 598 | + {$td->FILE => "split-nntree.out", $td->EXIT_STATUS => 0}, | ||
| 599 | + $td->NORMALIZE_NEWLINES); | ||
| 600 | +$td->runtest("check file", | ||
| 601 | + {$td->FILE => "a.pdf"}, | ||
| 602 | + {$td->FILE => "split-nntree-out.pdf"}); | ||
| 596 | 603 | ||
| 597 | show_ntests(); | 604 | show_ntests(); |
| 598 | # ---------- | 605 | # ---------- |
qpdf/qtest/qpdf/name-tree.out
| @@ -16,4 +16,32 @@ | @@ -16,4 +16,32 @@ | ||
| 16 | 20 twenty -> twenty. | 16 | 20 twenty -> twenty. |
| 17 | 22 twenty-two -> twenty-two! | 17 | 22 twenty-two -> twenty-two! |
| 18 | 29 twenty-nine -> twenty-nine! | 18 | 29 twenty-nine -> twenty-nine! |
| 19 | +/Empty1 | ||
| 20 | +/Empty2 | ||
| 21 | +/Bad1: deprecated API | ||
| 22 | +Name/Number tree node (object 16): item at index 2 is not the right type | ||
| 23 | +/Bad1 -- wrong key type | ||
| 24 | +WARNING: name-tree.pdf (Name/Number tree node (object 16)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 16)): item at index 2 is not the right type | ||
| 25 | +WARNING: name-tree.pdf (Name/Number tree node (object 16)): item 2 has the wrong type | ||
| 26 | +A | ||
| 27 | +Q | ||
| 28 | +Z | ||
| 29 | +/Bad2 -- invalid kid | ||
| 30 | +WARNING: name-tree.pdf (Name/Number tree node (object 17)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 19)): bad node during find | ||
| 31 | +WARNING: name-tree.pdf (Name/Number tree node (object 17)): skipping over invalid kid at index 1 | ||
| 32 | +B | ||
| 33 | +W | ||
| 34 | +/Bad3 -- invalid kid | ||
| 35 | +WARNING: name-tree.pdf (Name/Number tree node (object 25)): non-dictionary node while traversing name/number tree | ||
| 36 | +/Bad4 -- invalid kid | ||
| 37 | +WARNING: name-tree.pdf (Name/Number tree node (object 23)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 23)): invalid kid at index 1 | ||
| 38 | +WARNING: name-tree.pdf (Name/Number tree node (object 23)): skipping over invalid kid at index 1 | ||
| 39 | +C | ||
| 40 | +Q | ||
| 41 | +Z | ||
| 42 | +/Bad5 -- loop in find | ||
| 43 | +WARNING: name-tree.pdf (Name/Number tree node (object 28)): attempting to repair after error: name-tree.pdf (Name/Number tree node (object 30)): loop detected in find | ||
| 44 | +WARNING: name-tree.pdf (Name/Number tree node (object 30)): loop detected while traversing name/number tree | ||
| 45 | +/Bad6 -- bad limits | ||
| 46 | +WARNING: name-tree.pdf (Name/Number tree node (object 32)): unable to determine limits | ||
| 19 | test 48 done | 47 | test 48 done |
qpdf/qtest/qpdf/name-tree.pdf
| @@ -139,9 +139,219 @@ endobj | @@ -139,9 +139,219 @@ endobj | ||
| 139 | >> | 139 | >> |
| 140 | endobj | 140 | endobj |
| 141 | 141 | ||
| 142 | +13 0 obj | ||
| 143 | +<< | ||
| 144 | + /Names [ | ||
| 145 | + ] | ||
| 146 | +>> | ||
| 147 | +endobj | ||
| 148 | + | ||
| 149 | +14 0 obj | ||
| 150 | +<< | ||
| 151 | + /Kids [ | ||
| 152 | + 15 0 R | ||
| 153 | + ] | ||
| 154 | +>> | ||
| 155 | +endobj | ||
| 156 | + | ||
| 157 | +15 0 obj | ||
| 158 | +<< | ||
| 159 | + /Names [ | ||
| 160 | + ] | ||
| 161 | +>> | ||
| 162 | +endobj | ||
| 163 | + | ||
| 164 | +16 0 obj | ||
| 165 | +<< | ||
| 166 | + /Names [ | ||
| 167 | + (A) (A) | ||
| 168 | + 6 (F) | ||
| 169 | + (Q) (Q) | ||
| 170 | + (Z) (Z) | ||
| 171 | + ] | ||
| 172 | +>> | ||
| 173 | +endobj | ||
| 174 | + | ||
| 175 | +17 0 obj | ||
| 176 | +<< | ||
| 177 | + /Kids [ | ||
| 178 | + 18 0 R | ||
| 179 | + 19 0 R | ||
| 180 | + 20 0 R | ||
| 181 | + ] | ||
| 182 | +>> | ||
| 183 | +endobj | ||
| 184 | + | ||
| 185 | +18 0 obj | ||
| 186 | +<< | ||
| 187 | + /Limits [ (B) (B) ] | ||
| 188 | + /Names [ | ||
| 189 | + (B) (B) | ||
| 190 | + ] | ||
| 191 | +>> | ||
| 192 | +endobj | ||
| 193 | + | ||
| 194 | +19 0 obj | ||
| 195 | +<< | ||
| 196 | + /Limits [ (F) (H) ] | ||
| 197 | + /X (oops) | ||
| 198 | +>> | ||
| 199 | +endobj | ||
| 200 | + | ||
| 201 | +20 0 obj | ||
| 202 | +<< | ||
| 203 | + /Limits [ (W) (W) ] | ||
| 204 | + /Names [ | ||
| 205 | + (W) (W) | ||
| 206 | + ] | ||
| 207 | +>> | ||
| 208 | +endobj | ||
| 209 | + | ||
| 210 | +21 0 obj | ||
| 211 | +<< | ||
| 212 | + /Kids [ | ||
| 213 | + 22 0 R | ||
| 214 | + ] | ||
| 215 | +>> | ||
| 216 | +endobj | ||
| 217 | + | ||
| 218 | +22 0 obj | ||
| 219 | +<< | ||
| 220 | + /Limits [ (A) (Z) ] | ||
| 221 | + /Kids [ | ||
| 222 | + 25 0 R | ||
| 223 | + ] | ||
| 224 | +>> | ||
| 225 | +endobj | ||
| 226 | + | ||
| 227 | +23 0 obj | ||
| 228 | +<< | ||
| 229 | + /Kids [ | ||
| 230 | + 24 0 R | ||
| 231 | + 25 0 R | ||
| 232 | + 26 0 R | ||
| 233 | + 27 0 R | ||
| 234 | + ] | ||
| 235 | +>> | ||
| 236 | +endobj | ||
| 237 | + | ||
| 238 | +24 0 obj | ||
| 239 | +<< | ||
| 240 | + /Limits [ (C) (C) ] | ||
| 241 | + /Names [ | ||
| 242 | + (C) (C) | ||
| 243 | + ] | ||
| 244 | +>> | ||
| 245 | +endobj | ||
| 246 | + | ||
| 247 | +25 0 obj | ||
| 248 | +(oops) | ||
| 249 | +endobj | ||
| 250 | + | ||
| 251 | +26 0 obj | ||
| 252 | +<< | ||
| 253 | + /Limits [ (Q) (Q) ] | ||
| 254 | + /Names [ | ||
| 255 | + (Q) (Q) | ||
| 256 | + ] | ||
| 257 | +>> | ||
| 258 | +endobj | ||
| 259 | + | ||
| 260 | +27 0 obj | ||
| 261 | +<< | ||
| 262 | + /Limits [ (Z) (Z) ] | ||
| 263 | + /Names [ | ||
| 264 | + (Z) (Z) | ||
| 265 | + ] | ||
| 266 | +>> | ||
| 267 | +endobj | ||
| 268 | + | ||
| 269 | +28 0 obj | ||
| 270 | +<< | ||
| 271 | + /Kids [ | ||
| 272 | + 29 0 R | ||
| 273 | + 30 0 R | ||
| 274 | + ] | ||
| 275 | +>> | ||
| 276 | +endobj | ||
| 277 | + | ||
| 278 | +29 0 obj | ||
| 279 | +<< | ||
| 280 | + /Limits [ (D) (D) ] | ||
| 281 | + /Names [ | ||
| 282 | + (D) (D) | ||
| 283 | + ] | ||
| 284 | +>> | ||
| 285 | +endobj | ||
| 286 | + | ||
| 287 | +30 0 obj | ||
| 288 | +<< | ||
| 289 | + /Limits [ (E) (Z) ] | ||
| 290 | + /Kids [ | ||
| 291 | + 30 0 R | ||
| 292 | + ] | ||
| 293 | +>> | ||
| 294 | +endobj | ||
| 295 | + | ||
| 296 | +31 0 obj | ||
| 297 | +<< | ||
| 298 | + /Kids [ | ||
| 299 | + 32 0 R | ||
| 300 | + ] | ||
| 301 | +>> | ||
| 302 | +endobj | ||
| 303 | + | ||
| 304 | +32 0 obj | ||
| 305 | +<< | ||
| 306 | + /Limits [ (E) (Z) ] | ||
| 307 | + /Kids [ | ||
| 308 | + 33 0 R | ||
| 309 | + 34 0 R | ||
| 310 | + 35 0 R | ||
| 311 | + 36 0 R | ||
| 312 | + ] | ||
| 313 | +>> | ||
| 314 | +endobj | ||
| 315 | + | ||
| 316 | +33 0 obj | ||
| 317 | +<< | ||
| 318 | + /Limits [ (E) (G) ] | ||
| 319 | + /Names [ | ||
| 320 | + (E) (E) | ||
| 321 | + (G) (G) | ||
| 322 | + ] | ||
| 323 | +>> | ||
| 324 | +endobj | ||
| 325 | + | ||
| 326 | +34 0 obj | ||
| 327 | +<< | ||
| 328 | + /Limits [ (N) (N) ] | ||
| 329 | + /Names [ | ||
| 330 | + (N) (N) | ||
| 331 | + ] | ||
| 332 | +>> | ||
| 333 | +endobj | ||
| 334 | + | ||
| 335 | +35 0 obj | ||
| 336 | +<< | ||
| 337 | + /Limits [ (O) (O) ] | ||
| 338 | + /Names [ | ||
| 339 | + (O) (O) | ||
| 340 | + ] | ||
| 341 | +>> | ||
| 342 | +endobj | ||
| 343 | + | ||
| 344 | +36 0 obj | ||
| 345 | +<< | ||
| 346 | + /Limits [ (bad) ] | ||
| 347 | + /Names [ | ||
| 348 | + (Q) (Q) | ||
| 349 | + ] | ||
| 350 | +>> | ||
| 351 | +endobj | ||
| 142 | 352 | ||
| 143 | xref | 353 | xref |
| 144 | -0 13 | 354 | +0 37 |
| 145 | 0000000000 65535 f | 355 | 0000000000 65535 f |
| 146 | 0000000025 00000 n | 356 | 0000000025 00000 n |
| 147 | 0000000079 00000 n | 357 | 0000000079 00000 n |
| @@ -155,12 +365,44 @@ xref | @@ -155,12 +365,44 @@ xref | ||
| 155 | 0000000808 00000 n | 365 | 0000000808 00000 n |
| 156 | 0000000995 00000 n | 366 | 0000000995 00000 n |
| 157 | 0000001191 00000 n | 367 | 0000001191 00000 n |
| 368 | +0000001364 00000 n | ||
| 369 | +0000001402 00000 n | ||
| 370 | +0000001450 00000 n | ||
| 371 | +0000001488 00000 n | ||
| 372 | +0000001572 00000 n | ||
| 373 | +0000001642 00000 n | ||
| 374 | +0000001714 00000 n | ||
| 375 | +0000001771 00000 n | ||
| 376 | +0000001843 00000 n | ||
| 377 | +0000001891 00000 n | ||
| 378 | +0000001961 00000 n | ||
| 379 | +0000002042 00000 n | ||
| 380 | +0000002114 00000 n | ||
| 381 | +0000002138 00000 n | ||
| 382 | +0000002210 00000 n | ||
| 383 | +0000002282 00000 n | ||
| 384 | +0000002341 00000 n | ||
| 385 | +0000002413 00000 n | ||
| 386 | +0000002483 00000 n | ||
| 387 | +0000002531 00000 n | ||
| 388 | +0000002634 00000 n | ||
| 389 | +0000002718 00000 n | ||
| 390 | +0000002790 00000 n | ||
| 391 | +0000002862 00000 n | ||
| 158 | trailer << | 392 | trailer << |
| 159 | /Root 1 0 R | 393 | /Root 1 0 R |
| 160 | /QTest 8 0 R | 394 | /QTest 8 0 R |
| 161 | - /Size 13 | 395 | + /Empty1 13 0 R |
| 396 | + /Empty2 14 0 R | ||
| 397 | + /Bad1 16 0 R | ||
| 398 | + /Bad2 17 0 R | ||
| 399 | + /Bad3 21 0 R | ||
| 400 | + /Bad4 23 0 R | ||
| 401 | + /Bad5 28 0 R | ||
| 402 | + /Bad6 31 0 R | ||
| 403 | + /Size 37 | ||
| 162 | /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] | 404 | /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] |
| 163 | >> | 405 | >> |
| 164 | startxref | 406 | startxref |
| 165 | -1365 | 407 | +2932 |
| 166 | %%EOF | 408 | %%EOF |
qpdf/qtest/qpdf/number-tree.out
| @@ -26,6 +26,39 @@ | @@ -26,6 +26,39 @@ | ||
| 26 | 22 twenty-two | 26 | 22 twenty-two |
| 27 | 23 twenty-three | 27 | 23 twenty-three |
| 28 | 29 twenty-nine | 28 | 29 twenty-nine |
| 29 | -WARNING: number-tree.pdf (Name/Number tree node (object 14)): name/number tree node has neither /Kids nor /Names | 29 | +/Bad1: deprecated API |
| 30 | +/Bad1 | ||
| 31 | +WARNING: number-tree.pdf (Name/Number tree node (object 14)): name/number tree node has neither non-empty /Nums nor /Kids | ||
| 30 | WARNING: number-tree.pdf (Name/Number tree node (object 13)): loop detected while traversing name/number tree | 32 | WARNING: number-tree.pdf (Name/Number tree node (object 13)): loop detected while traversing name/number tree |
| 33 | +/Bad2 | ||
| 34 | +10 (10) | ||
| 35 | +WARNING: number-tree.pdf (Name/Number tree node (object 16)): item 2 has the wrong type | ||
| 36 | +15 (15) | ||
| 37 | +WARNING: number-tree.pdf (Name/Number tree node (object 16)): items array doesn't have enough elements | ||
| 38 | +WARNING: number-tree.pdf (Name/Number tree node (object 15)): skipping over invalid kid at index 1 | ||
| 39 | +WARNING: number-tree.pdf (Name/Number tree node (object 17)): name/number tree node has neither non-empty /Nums nor /Kids | ||
| 40 | +35 (35) | ||
| 41 | +38 (38) | ||
| 42 | +WARNING: number-tree.pdf (Name/Number tree node (object 19)): name/number tree node has neither non-empty /Nums nor /Kids | ||
| 43 | +/Empty1 | ||
| 44 | +/Empty2 | ||
| 45 | +Insert into invalid | ||
| 46 | +WARNING: number-tree.pdf (Name/Number tree node): name/number tree node has neither non-empty /Nums nor /Kids | ||
| 47 | +WARNING: number-tree.pdf (Name/Number tree node): name/number tree node has neither non-empty /Nums nor /Kids | ||
| 48 | +number-tree.pdf (Name/Number tree node): unable to find a valid items node | ||
| 49 | +/Bad3, no repair | ||
| 50 | +WARNING: number-tree.pdf (Name/Number tree node (object 23)): kid number 0 is not an indirect object | ||
| 51 | +0 (zero) | ||
| 52 | +10 (ten) | ||
| 53 | +/Bad3, repair | ||
| 54 | +WARNING: number-tree.pdf (Name/Number tree node (object 23)): converting kid number 0 to an indirect object | ||
| 55 | +0 (zero) | ||
| 56 | +10 (ten) | ||
| 57 | +/Bad4 -- missing limits | ||
| 58 | +WARNING: number-tree.pdf (Name/Number tree node (object 24)): attempting to repair after error: number-tree.pdf (Name/Number tree node (object 25)): node is missing /Limits | ||
| 59 | +0 (0) | ||
| 60 | +5 (5) | ||
| 61 | +10 (10) | ||
| 62 | +/Bad5 -- limit errors | ||
| 63 | +WARNING: number-tree.pdf (Name/Number tree node (object 28)): attempting to repair after error: number-tree.pdf (Name/Number tree node (object 29)): unexpected -1 from binary search of kids; limits may by wrong | ||
| 31 | test 46 done | 64 | test 46 done |
qpdf/qtest/qpdf/number-tree.pdf
| @@ -158,8 +158,155 @@ endobj | @@ -158,8 +158,155 @@ endobj | ||
| 158 | >> | 158 | >> |
| 159 | endobj | 159 | endobj |
| 160 | 160 | ||
| 161 | +15 0 obj | ||
| 162 | +<< | ||
| 163 | + /Kids [ | ||
| 164 | + 16 0 R | ||
| 165 | + 14 0 R | ||
| 166 | + 17 0 R | ||
| 167 | + 18 0 R | ||
| 168 | + 19 0 R | ||
| 169 | + ] | ||
| 170 | +>> | ||
| 171 | +endobj | ||
| 172 | + | ||
| 173 | +16 0 obj | ||
| 174 | +<< | ||
| 175 | + /Limits [ 10 20 ] | ||
| 176 | + /Nums [ | ||
| 177 | + 10 (10) | ||
| 178 | + (12) (12) | ||
| 179 | + 15 (15) | ||
| 180 | + 20 | ||
| 181 | + ] | ||
| 182 | +>> | ||
| 183 | +endobj | ||
| 184 | + | ||
| 185 | +17 0 obj | ||
| 186 | +<< | ||
| 187 | + /Limits [ 25 25 ] | ||
| 188 | + /Nums [ | ||
| 189 | + ] | ||
| 190 | +>> | ||
| 191 | +endobj | ||
| 192 | + | ||
| 193 | +18 0 obj | ||
| 194 | +<< | ||
| 195 | + /Limits [ 35 35 ] | ||
| 196 | + /Nums [ | ||
| 197 | + 35 (35) | ||
| 198 | + 38 (38) | ||
| 199 | + ] | ||
| 200 | +>> | ||
| 201 | +endobj | ||
| 202 | + | ||
| 203 | +19 0 obj | ||
| 204 | +<< | ||
| 205 | + /Limits [ 40 40 ] | ||
| 206 | + /Nums [ | ||
| 207 | + ] | ||
| 208 | +>> | ||
| 209 | +endobj | ||
| 210 | + | ||
| 211 | +20 0 obj | ||
| 212 | +<< | ||
| 213 | + /Nums [ | ||
| 214 | + ] | ||
| 215 | +>> | ||
| 216 | +endobj | ||
| 217 | + | ||
| 218 | +21 0 obj | ||
| 219 | +<< | ||
| 220 | + /Kids [ | ||
| 221 | + 22 0 R | ||
| 222 | + ] | ||
| 223 | +>> | ||
| 224 | +endobj | ||
| 225 | + | ||
| 226 | +22 0 obj | ||
| 227 | +<< | ||
| 228 | + /Nums [ | ||
| 229 | + ] | ||
| 230 | +>> | ||
| 231 | +endobj | ||
| 232 | + | ||
| 233 | +23 0 obj | ||
| 234 | +<< | ||
| 235 | + /Kids [ | ||
| 236 | + << | ||
| 237 | + /Limits [ 0 10 ] | ||
| 238 | + /Nums [ | ||
| 239 | + 0 (zero) | ||
| 240 | + 10 (ten) | ||
| 241 | + ] | ||
| 242 | + >> | ||
| 243 | + ] | ||
| 244 | +>> | ||
| 245 | +endobj | ||
| 246 | + | ||
| 247 | +24 0 obj | ||
| 248 | +<< | ||
| 249 | + /Kids [ | ||
| 250 | + 25 0 R | ||
| 251 | + ] | ||
| 252 | +>> | ||
| 253 | +endobj | ||
| 254 | + | ||
| 255 | +25 0 obj | ||
| 256 | +<< | ||
| 257 | + /Kids [ | ||
| 258 | + 26 0 R | ||
| 259 | + 27 0 R | ||
| 260 | + ] | ||
| 261 | +>> | ||
| 262 | +endobj | ||
| 263 | + | ||
| 264 | +26 0 obj | ||
| 265 | +<< | ||
| 266 | + /Nums [ | ||
| 267 | + 0 (0) | ||
| 268 | + ] | ||
| 269 | +>> | ||
| 270 | +endobj | ||
| 271 | + | ||
| 272 | +27 0 obj | ||
| 273 | +<< | ||
| 274 | + /Nums [ | ||
| 275 | + 10 (10) | ||
| 276 | + ] | ||
| 277 | +>> | ||
| 278 | +endobj | ||
| 279 | + | ||
| 280 | +28 0 obj | ||
| 281 | +<< | ||
| 282 | + /Kids [ | ||
| 283 | + 29 0 R | ||
| 284 | + ] | ||
| 285 | +>> | ||
| 286 | +endobj | ||
| 287 | + | ||
| 288 | +29 0 obj | ||
| 289 | +<< | ||
| 290 | + /Limits [ 5 15 ] | ||
| 291 | + /Kids [ | ||
| 292 | + 30 0 R | ||
| 293 | + ] | ||
| 294 | +>> | ||
| 295 | +endobj | ||
| 296 | + | ||
| 297 | +30 0 obj | ||
| 298 | +<< | ||
| 299 | + /Limits [ 20 30 ] | ||
| 300 | + /Nums [ | ||
| 301 | + 2 (2) | ||
| 302 | + 20 (20) | ||
| 303 | + 30 (30) | ||
| 304 | + ] | ||
| 305 | +>> | ||
| 306 | +endobj | ||
| 307 | + | ||
| 161 | xref | 308 | xref |
| 162 | -0 15 | 309 | +0 31 |
| 163 | 0000000000 65535 f | 310 | 0000000000 65535 f |
| 164 | 0000000025 00000 n | 311 | 0000000025 00000 n |
| 165 | 0000000079 00000 n | 312 | 0000000079 00000 n |
| @@ -175,13 +322,35 @@ xref | @@ -175,13 +322,35 @@ xref | ||
| 175 | 0000001078 00000 n | 322 | 0000001078 00000 n |
| 176 | 0000001214 00000 n | 323 | 0000001214 00000 n |
| 177 | 0000001273 00000 n | 324 | 0000001273 00000 n |
| 325 | +0000001296 00000 n | ||
| 326 | +0000001388 00000 n | ||
| 327 | +0000001490 00000 n | ||
| 328 | +0000001547 00000 n | ||
| 329 | +0000001628 00000 n | ||
| 330 | +0000001685 00000 n | ||
| 331 | +0000001722 00000 n | ||
| 332 | +0000001770 00000 n | ||
| 333 | +0000001807 00000 n | ||
| 334 | +0000001937 00000 n | ||
| 335 | +0000001985 00000 n | ||
| 336 | +0000002044 00000 n | ||
| 337 | +0000002091 00000 n | ||
| 338 | +0000002140 00000 n | ||
| 339 | +0000002188 00000 n | ||
| 340 | +0000002255 00000 n | ||
| 178 | trailer << | 341 | trailer << |
| 179 | /Root 1 0 R | 342 | /Root 1 0 R |
| 180 | /QTest 8 0 R | 343 | /QTest 8 0 R |
| 181 | /Bad1 13 0 R | 344 | /Bad1 13 0 R |
| 182 | - /Size 15 | 345 | + /Bad2 15 0 R |
| 346 | + /Bad3 23 0 R | ||
| 347 | + /Bad4 24 0 R | ||
| 348 | + /Bad5 28 0 R | ||
| 349 | + /Empty1 20 0 R | ||
| 350 | + /Empty2 21 0 R | ||
| 351 | + /Size 31 | ||
| 183 | /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] | 352 | /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] |
| 184 | >> | 353 | >> |
| 185 | startxref | 354 | startxref |
| 186 | -1296 | 355 | +2346 |
| 187 | %%EOF | 356 | %%EOF |
qpdf/qtest/qpdf/split-nntree-out.pdf
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 5 0 R | ||
| 9 | + /Type /Catalog | ||
| 10 | +>> | ||
| 11 | +endobj | ||
| 12 | + | ||
| 13 | +%% Original object ID: 8 0 | ||
| 14 | +2 0 obj | ||
| 15 | +<< | ||
| 16 | + /Kids [ | ||
| 17 | + 6 0 R | ||
| 18 | + 7 0 R | ||
| 19 | + ] | ||
| 20 | +>> | ||
| 21 | +endobj | ||
| 22 | + | ||
| 23 | +%% Original object ID: 17 0 | ||
| 24 | +3 0 obj | ||
| 25 | +<< | ||
| 26 | + /Kids [ | ||
| 27 | + 8 0 R | ||
| 28 | + 9 0 R | ||
| 29 | + ] | ||
| 30 | +>> | ||
| 31 | +endobj | ||
| 32 | + | ||
| 33 | +%% Original object ID: 18 0 | ||
| 34 | +4 0 obj | ||
| 35 | +<< | ||
| 36 | + /Kids [ | ||
| 37 | + 10 0 R | ||
| 38 | + 11 0 R | ||
| 39 | + ] | ||
| 40 | +>> | ||
| 41 | +endobj | ||
| 42 | + | ||
| 43 | +%% Original object ID: 2 0 | ||
| 44 | +5 0 obj | ||
| 45 | +<< | ||
| 46 | + /Count 1 | ||
| 47 | + /Kids [ | ||
| 48 | + 12 0 R | ||
| 49 | + ] | ||
| 50 | + /Type /Pages | ||
| 51 | +>> | ||
| 52 | +endobj | ||
| 53 | + | ||
| 54 | +%% Original object ID: 20 0 | ||
| 55 | +6 0 obj | ||
| 56 | +<< | ||
| 57 | + /Kids [ | ||
| 58 | + 13 0 R | ||
| 59 | + 14 0 R | ||
| 60 | + ] | ||
| 61 | + /Limits [ | ||
| 62 | + 10 | ||
| 63 | + 40 | ||
| 64 | + ] | ||
| 65 | +>> | ||
| 66 | +endobj | ||
| 67 | + | ||
| 68 | +%% Original object ID: 21 0 | ||
| 69 | +7 0 obj | ||
| 70 | +<< | ||
| 71 | + /Kids [ | ||
| 72 | + 15 0 R | ||
| 73 | + 16 0 R | ||
| 74 | + 17 0 R | ||
| 75 | + 18 0 R | ||
| 76 | + ] | ||
| 77 | + /Limits [ | ||
| 78 | + 50 | ||
| 79 | + 170 | ||
| 80 | + ] | ||
| 81 | +>> | ||
| 82 | +endobj | ||
| 83 | + | ||
| 84 | +%% Original object ID: 24 0 | ||
| 85 | +8 0 obj | ||
| 86 | +<< | ||
| 87 | + /Limits [ | ||
| 88 | + (A) | ||
| 89 | + (C) | ||
| 90 | + ] | ||
| 91 | + /Names [ | ||
| 92 | + (A) | ||
| 93 | + (A) | ||
| 94 | + (C) | ||
| 95 | + (C) | ||
| 96 | + ] | ||
| 97 | +>> | ||
| 98 | +endobj | ||
| 99 | + | ||
| 100 | +%% Original object ID: 25 0 | ||
| 101 | +9 0 obj | ||
| 102 | +<< | ||
| 103 | + /Limits [ | ||
| 104 | + (F) | ||
| 105 | + (Q) | ||
| 106 | + ] | ||
| 107 | + /Names [ | ||
| 108 | + (F) | ||
| 109 | + (F) | ||
| 110 | + (L) | ||
| 111 | + (L) | ||
| 112 | + (Q) | ||
| 113 | + (Q) | ||
| 114 | + ] | ||
| 115 | +>> | ||
| 116 | +endobj | ||
| 117 | + | ||
| 118 | +%% Original object ID: 26 0 | ||
| 119 | +10 0 obj | ||
| 120 | +<< | ||
| 121 | + /Limits [ | ||
| 122 | + (A) | ||
| 123 | + (F) | ||
| 124 | + ] | ||
| 125 | + /Names [ | ||
| 126 | + (A) | ||
| 127 | + (A) | ||
| 128 | + (F) | ||
| 129 | + (F) | ||
| 130 | + ] | ||
| 131 | +>> | ||
| 132 | +endobj | ||
| 133 | + | ||
| 134 | +%% Original object ID: 27 0 | ||
| 135 | +11 0 obj | ||
| 136 | +<< | ||
| 137 | + /Limits [ | ||
| 138 | + (L) | ||
| 139 | + <feff03c0> | ||
| 140 | + ] | ||
| 141 | + /Names [ | ||
| 142 | + (L) | ||
| 143 | + (L) | ||
| 144 | + (P) | ||
| 145 | + (P) | ||
| 146 | + (Q) | ||
| 147 | + (Q) | ||
| 148 | + <feff03c0> | ||
| 149 | + <feff03c0> | ||
| 150 | + ] | ||
| 151 | +>> | ||
| 152 | +endobj | ||
| 153 | + | ||
| 154 | +%% Page 1 | ||
| 155 | +%% Original object ID: 3 0 | ||
| 156 | +12 0 obj | ||
| 157 | +<< | ||
| 158 | + /Contents 19 0 R | ||
| 159 | + /MediaBox [ | ||
| 160 | + 0 | ||
| 161 | + 0 | ||
| 162 | + 612 | ||
| 163 | + 792 | ||
| 164 | + ] | ||
| 165 | + /Parent 5 0 R | ||
| 166 | + /Resources << | ||
| 167 | + /Font << | ||
| 168 | + /F1 21 0 R | ||
| 169 | + >> | ||
| 170 | + /ProcSet 22 0 R | ||
| 171 | + >> | ||
| 172 | + /Type /Page | ||
| 173 | +>> | ||
| 174 | +endobj | ||
| 175 | + | ||
| 176 | +%% Original object ID: 9 0 | ||
| 177 | +13 0 obj | ||
| 178 | +<< | ||
| 179 | + /Limits [ | ||
| 180 | + 10 | ||
| 181 | + 15 | ||
| 182 | + ] | ||
| 183 | + /Nums [ | ||
| 184 | + 10 | ||
| 185 | + (10) | ||
| 186 | + 15 | ||
| 187 | + (15) | ||
| 188 | + ] | ||
| 189 | +>> | ||
| 190 | +endobj | ||
| 191 | + | ||
| 192 | +%% Original object ID: 19 0 | ||
| 193 | +14 0 obj | ||
| 194 | +<< | ||
| 195 | + /Limits [ | ||
| 196 | + 20 | ||
| 197 | + 40 | ||
| 198 | + ] | ||
| 199 | + /Nums [ | ||
| 200 | + 20 | ||
| 201 | + (20) | ||
| 202 | + 30 | ||
| 203 | + (30) | ||
| 204 | + 35 | ||
| 205 | + (35) | ||
| 206 | + 40 | ||
| 207 | + (40) | ||
| 208 | + ] | ||
| 209 | +>> | ||
| 210 | +endobj | ||
| 211 | + | ||
| 212 | +%% Original object ID: 10 0 | ||
| 213 | +15 0 obj | ||
| 214 | +<< | ||
| 215 | + /Limits [ | ||
| 216 | + 50 | ||
| 217 | + 80 | ||
| 218 | + ] | ||
| 219 | + /Nums [ | ||
| 220 | + 50 | ||
| 221 | + (50) | ||
| 222 | + 60 | ||
| 223 | + (60) | ||
| 224 | + 70 | ||
| 225 | + (70) | ||
| 226 | + 80 | ||
| 227 | + (80) | ||
| 228 | + ] | ||
| 229 | +>> | ||
| 230 | +endobj | ||
| 231 | + | ||
| 232 | +%% Original object ID: 11 0 | ||
| 233 | +16 0 obj | ||
| 234 | +<< | ||
| 235 | + /Kids [ | ||
| 236 | + 23 0 R | ||
| 237 | + 24 0 R | ||
| 238 | + ] | ||
| 239 | + /Limits [ | ||
| 240 | + 90 | ||
| 241 | + 100 | ||
| 242 | + ] | ||
| 243 | +>> | ||
| 244 | +endobj | ||
| 245 | + | ||
| 246 | +%% Original object ID: 23 0 | ||
| 247 | +17 0 obj | ||
| 248 | +<< | ||
| 249 | + /Kids [ | ||
| 250 | + 25 0 R | ||
| 251 | + 26 0 R | ||
| 252 | + 27 0 R | ||
| 253 | + ] | ||
| 254 | + /Limits [ | ||
| 255 | + 110 | ||
| 256 | + 160 | ||
| 257 | + ] | ||
| 258 | +>> | ||
| 259 | +endobj | ||
| 260 | + | ||
| 261 | +%% Original object ID: 16 0 | ||
| 262 | +18 0 obj | ||
| 263 | +<< | ||
| 264 | + /Limits [ | ||
| 265 | + 170 | ||
| 266 | + 170 | ||
| 267 | + ] | ||
| 268 | + /Nums [ | ||
| 269 | + 170 | ||
| 270 | + (170) | ||
| 271 | + ] | ||
| 272 | +>> | ||
| 273 | +endobj | ||
| 274 | + | ||
| 275 | +%% Contents for page 1 | ||
| 276 | +%% Original object ID: 4 0 | ||
| 277 | +19 0 obj | ||
| 278 | +<< | ||
| 279 | + /Length 20 0 R | ||
| 280 | +>> | ||
| 281 | +stream | ||
| 282 | +BT | ||
| 283 | + /F1 24 Tf | ||
| 284 | + 72 720 Td | ||
| 285 | + (Potato) Tj | ||
| 286 | +ET | ||
| 287 | +endstream | ||
| 288 | +endobj | ||
| 289 | + | ||
| 290 | +20 0 obj | ||
| 291 | +44 | ||
| 292 | +endobj | ||
| 293 | + | ||
| 294 | +%% Original object ID: 6 0 | ||
| 295 | +21 0 obj | ||
| 296 | +<< | ||
| 297 | + /BaseFont /Helvetica | ||
| 298 | + /Encoding /WinAnsiEncoding | ||
| 299 | + /Name /F1 | ||
| 300 | + /Subtype /Type1 | ||
| 301 | + /Type /Font | ||
| 302 | +>> | ||
| 303 | +endobj | ||
| 304 | + | ||
| 305 | +%% Original object ID: 7 0 | ||
| 306 | +22 0 obj | ||
| 307 | +[ | ||
| 308 | |||
| 309 | + /Text | ||
| 310 | +] | ||
| 311 | +endobj | ||
| 312 | + | ||
| 313 | +%% Original object ID: 12 0 | ||
| 314 | +23 0 obj | ||
| 315 | +<< | ||
| 316 | + /Limits [ | ||
| 317 | + 90 | ||
| 318 | + 90 | ||
| 319 | + ] | ||
| 320 | + /Nums [ | ||
| 321 | + 90 | ||
| 322 | + (90) | ||
| 323 | + ] | ||
| 324 | +>> | ||
| 325 | +endobj | ||
| 326 | + | ||
| 327 | +%% Original object ID: 13 0 | ||
| 328 | +24 0 obj | ||
| 329 | +<< | ||
| 330 | + /Limits [ | ||
| 331 | + 100 | ||
| 332 | + 100 | ||
| 333 | + ] | ||
| 334 | + /Nums [ | ||
| 335 | + 100 | ||
| 336 | + (100) | ||
| 337 | + ] | ||
| 338 | +>> | ||
| 339 | +endobj | ||
| 340 | + | ||
| 341 | +%% Original object ID: 14 0 | ||
| 342 | +25 0 obj | ||
| 343 | +<< | ||
| 344 | + /Limits [ | ||
| 345 | + 110 | ||
| 346 | + 120 | ||
| 347 | + ] | ||
| 348 | + /Nums [ | ||
| 349 | + 110 | ||
| 350 | + (110) | ||
| 351 | + 120 | ||
| 352 | + (120) | ||
| 353 | + ] | ||
| 354 | +>> | ||
| 355 | +endobj | ||
| 356 | + | ||
| 357 | +%% Original object ID: 22 0 | ||
| 358 | +26 0 obj | ||
| 359 | +<< | ||
| 360 | + /Limits [ | ||
| 361 | + 125 | ||
| 362 | + 140 | ||
| 363 | + ] | ||
| 364 | + /Nums [ | ||
| 365 | + 125 | ||
| 366 | + (125) | ||
| 367 | + 130 | ||
| 368 | + (130) | ||
| 369 | + 140 | ||
| 370 | + (140) | ||
| 371 | + ] | ||
| 372 | +>> | ||
| 373 | +endobj | ||
| 374 | + | ||
| 375 | +%% Original object ID: 15 0 | ||
| 376 | +27 0 obj | ||
| 377 | +<< | ||
| 378 | + /Limits [ | ||
| 379 | + 150 | ||
| 380 | + 160 | ||
| 381 | + ] | ||
| 382 | + /Nums [ | ||
| 383 | + 150 | ||
| 384 | + (150) | ||
| 385 | + 160 | ||
| 386 | + (160) | ||
| 387 | + ] | ||
| 388 | +>> | ||
| 389 | +endobj | ||
| 390 | + | ||
| 391 | +xref | ||
| 392 | +0 28 | ||
| 393 | +0000000000 65535 f | ||
| 394 | +0000000052 00000 n | ||
| 395 | +0000000133 00000 n | ||
| 396 | +0000000217 00000 n | ||
| 397 | +0000000301 00000 n | ||
| 398 | +0000000386 00000 n | ||
| 399 | +0000000487 00000 n | ||
| 400 | +0000000603 00000 n | ||
| 401 | +0000000742 00000 n | ||
| 402 | +0000000871 00000 n | ||
| 403 | +0000001016 00000 n | ||
| 404 | +0000001146 00000 n | ||
| 405 | +0000001338 00000 n | ||
| 406 | +0000001561 00000 n | ||
| 407 | +0000001688 00000 n | ||
| 408 | +0000001847 00000 n | ||
| 409 | +0000002006 00000 n | ||
| 410 | +0000002124 00000 n | ||
| 411 | +0000002254 00000 n | ||
| 412 | +0000002391 00000 n | ||
| 413 | +0000002492 00000 n | ||
| 414 | +0000002539 00000 n | ||
| 415 | +0000002685 00000 n | ||
| 416 | +0000002749 00000 n | ||
| 417 | +0000002860 00000 n | ||
| 418 | +0000002975 00000 n | ||
| 419 | +0000003108 00000 n | ||
| 420 | +0000003259 00000 n | ||
| 421 | +trailer << | ||
| 422 | + /Root 1 0 R | ||
| 423 | + /Size 28 | ||
| 424 | + /Split1 2 0 R | ||
| 425 | + /Split2 3 0 R | ||
| 426 | + /Split3 4 0 R | ||
| 427 | + /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><31415926535897932384626433832795>] | ||
| 428 | +>> | ||
| 429 | +startxref | ||
| 430 | +3364 | ||
| 431 | +%%EOF |
qpdf/qtest/qpdf/split-nntree.out
0 → 100644
qpdf/qtest/qpdf/split-nntree.pdf
0 → 100644
| 1 | +%PDF-1.3 | ||
| 2 | +%¿÷¢þ | ||
| 3 | +%QDF-1.0 | ||
| 4 | + | ||
| 5 | +1 0 obj | ||
| 6 | +<< | ||
| 7 | + /Pages 2 0 R | ||
| 8 | + /Type /Catalog | ||
| 9 | +>> | ||
| 10 | +endobj | ||
| 11 | + | ||
| 12 | +2 0 obj | ||
| 13 | +<< | ||
| 14 | + /Count 1 | ||
| 15 | + /Kids [ | ||
| 16 | + 3 0 R | ||
| 17 | + ] | ||
| 18 | + /Type /Pages | ||
| 19 | +>> | ||
| 20 | +endobj | ||
| 21 | + | ||
| 22 | +%% Page 1 | ||
| 23 | +3 0 obj | ||
| 24 | +<< | ||
| 25 | + /Contents 4 0 R | ||
| 26 | + /MediaBox [ | ||
| 27 | + 0 | ||
| 28 | + 0 | ||
| 29 | + 612 | ||
| 30 | + 792 | ||
| 31 | + ] | ||
| 32 | + /Parent 2 0 R | ||
| 33 | + /Resources << | ||
| 34 | + /Font << | ||
| 35 | + /F1 6 0 R | ||
| 36 | + >> | ||
| 37 | + /ProcSet 7 0 R | ||
| 38 | + >> | ||
| 39 | + /Type /Page | ||
| 40 | +>> | ||
| 41 | +endobj | ||
| 42 | + | ||
| 43 | +%% Contents for page 1 | ||
| 44 | +4 0 obj | ||
| 45 | +<< | ||
| 46 | + /Length 5 0 R | ||
| 47 | +>> | ||
| 48 | +stream | ||
| 49 | +BT | ||
| 50 | + /F1 24 Tf | ||
| 51 | + 72 720 Td | ||
| 52 | + (Potato) Tj | ||
| 53 | +ET | ||
| 54 | +endstream | ||
| 55 | +endobj | ||
| 56 | + | ||
| 57 | +5 0 obj | ||
| 58 | +44 | ||
| 59 | +endobj | ||
| 60 | + | ||
| 61 | +6 0 obj | ||
| 62 | +<< | ||
| 63 | + /BaseFont /Helvetica | ||
| 64 | + /Encoding /WinAnsiEncoding | ||
| 65 | + /Name /F1 | ||
| 66 | + /Subtype /Type1 | ||
| 67 | + /Type /Font | ||
| 68 | +>> | ||
| 69 | +endobj | ||
| 70 | + | ||
| 71 | +7 0 obj | ||
| 72 | +[ | ||
| 73 | |||
| 74 | + /Text | ||
| 75 | +] | ||
| 76 | +endobj | ||
| 77 | + | ||
| 78 | +8 0 obj | ||
| 79 | +<< | ||
| 80 | + /Kids [ | ||
| 81 | + 9 0 R | ||
| 82 | + 10 0 R | ||
| 83 | + 11 0 R | ||
| 84 | + 16 0 R | ||
| 85 | + ] | ||
| 86 | +>> | ||
| 87 | +endobj | ||
| 88 | + | ||
| 89 | +9 0 obj | ||
| 90 | +<< | ||
| 91 | + /Limits [ 10 40 ] | ||
| 92 | + /Nums [ | ||
| 93 | + 10 (10) | ||
| 94 | + 20 (20) | ||
| 95 | + 30 (30) | ||
| 96 | + 40 (40) | ||
| 97 | + ] | ||
| 98 | +>> | ||
| 99 | +endobj | ||
| 100 | + | ||
| 101 | +10 0 obj | ||
| 102 | +<< | ||
| 103 | + /Limits [ 50 80 ] | ||
| 104 | + /Nums [ | ||
| 105 | + 50 (50) | ||
| 106 | + 60 (60) | ||
| 107 | + 70 (70) | ||
| 108 | + 80 (80) | ||
| 109 | + ] | ||
| 110 | +>> | ||
| 111 | +endobj | ||
| 112 | + | ||
| 113 | +11 0 obj | ||
| 114 | +<< | ||
| 115 | + /Limits [ 90 160 ] | ||
| 116 | + /Kids [ | ||
| 117 | + 12 0 R | ||
| 118 | + 13 0 R | ||
| 119 | + 14 0 R | ||
| 120 | + 15 0 R | ||
| 121 | + ] | ||
| 122 | +>> | ||
| 123 | +endobj | ||
| 124 | + | ||
| 125 | +12 0 obj | ||
| 126 | +<< | ||
| 127 | + /Limits [ 90 90 ] | ||
| 128 | + /Nums [ | ||
| 129 | + 90 (90) | ||
| 130 | + ] | ||
| 131 | +>> | ||
| 132 | +endobj | ||
| 133 | + | ||
| 134 | +13 0 obj | ||
| 135 | +<< | ||
| 136 | + /Limits [ 100 100 ] | ||
| 137 | + /Nums [ | ||
| 138 | + 100 (100) | ||
| 139 | + ] | ||
| 140 | +>> | ||
| 141 | +endobj | ||
| 142 | + | ||
| 143 | +14 0 obj | ||
| 144 | +<< | ||
| 145 | + /Limits [ 110 140 ] | ||
| 146 | + /Nums [ | ||
| 147 | + 110 (110) | ||
| 148 | + 120 (120) | ||
| 149 | + 130 (130) | ||
| 150 | + 140 (140) | ||
| 151 | + ] | ||
| 152 | +>> | ||
| 153 | +endobj | ||
| 154 | + | ||
| 155 | +15 0 obj | ||
| 156 | +<< | ||
| 157 | + /Limits [ 150 160 ] | ||
| 158 | + /Nums [ | ||
| 159 | + 150 (150) | ||
| 160 | + 160 (160) | ||
| 161 | + ] | ||
| 162 | +>> | ||
| 163 | +endobj | ||
| 164 | + | ||
| 165 | +16 0 obj | ||
| 166 | +<< | ||
| 167 | + /Limits [ 170 170 ] | ||
| 168 | + /Nums [ | ||
| 169 | + 170 (170) | ||
| 170 | + ] | ||
| 171 | +>> | ||
| 172 | +endobj | ||
| 173 | + | ||
| 174 | +17 0 obj | ||
| 175 | +<< | ||
| 176 | + /Names [ | ||
| 177 | + (A) (A) | ||
| 178 | + (F) (F) | ||
| 179 | + (L) (L) | ||
| 180 | + (Q) (Q) | ||
| 181 | + ] | ||
| 182 | +>> | ||
| 183 | +endobj | ||
| 184 | + | ||
| 185 | +18 0 obj | ||
| 186 | +<< | ||
| 187 | + /Names [ | ||
| 188 | + (A) (A) | ||
| 189 | + (F) (F) | ||
| 190 | + (L) (L) | ||
| 191 | + (Q) (Q) | ||
| 192 | + ] | ||
| 193 | +>> | ||
| 194 | +endobj | ||
| 195 | + | ||
| 196 | +xref | ||
| 197 | +0 19 | ||
| 198 | +0000000000 65535 f | ||
| 199 | +0000000025 00000 n | ||
| 200 | +0000000079 00000 n | ||
| 201 | +0000000161 00000 n | ||
| 202 | +0000000376 00000 n | ||
| 203 | +0000000475 00000 n | ||
| 204 | +0000000494 00000 n | ||
| 205 | +0000000612 00000 n | ||
| 206 | +0000000647 00000 n | ||
| 207 | +0000000726 00000 n | ||
| 208 | +0000000830 00000 n | ||
| 209 | +0000000935 00000 n | ||
| 210 | +0000001037 00000 n | ||
| 211 | +0000001106 00000 n | ||
| 212 | +0000001179 00000 n | ||
| 213 | +0000001294 00000 n | ||
| 214 | +0000001381 00000 n | ||
| 215 | +0000001454 00000 n | ||
| 216 | +0000001540 00000 n | ||
| 217 | +trailer << | ||
| 218 | + /Root 1 0 R | ||
| 219 | + /Split1 8 0 R | ||
| 220 | + /Split2 17 0 R | ||
| 221 | + /Split3 18 0 R | ||
| 222 | + /Size 19 | ||
| 223 | + /ID [<2c3b7a6ec7fc61db8a5db4eebf57f540><2c3b7a6ec7fc61db8a5db4eebf57f540>] | ||
| 224 | +>> | ||
| 225 | +startxref | ||
| 226 | +1626 | ||
| 227 | +%%EOF |
qpdf/test_driver.cc
| @@ -1777,14 +1777,92 @@ void runtest(int n, char const* filename1, char const* arg2) | @@ -1777,14 +1777,92 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 1777 | assert(2 == offset); | 1777 | assert(2 == offset); |
| 1778 | 1778 | ||
| 1779 | // Exercise deprecated API until qpdf 11 | 1779 | // Exercise deprecated API until qpdf 11 |
| 1780 | + std::cout << "/Bad1: deprecated API" << std::endl; | ||
| 1780 | auto bad1 = QPDFNumberTreeObjectHelper( | 1781 | auto bad1 = QPDFNumberTreeObjectHelper( |
| 1781 | pdf.getTrailer().getKey("/Bad1")); | 1782 | pdf.getTrailer().getKey("/Bad1")); |
| 1782 | assert(bad1.begin() == bad1.end()); | 1783 | assert(bad1.begin() == bad1.end()); |
| 1783 | 1784 | ||
| 1785 | + std::cout << "/Bad1" << std::endl; | ||
| 1784 | bad1 = QPDFNumberTreeObjectHelper( | 1786 | bad1 = QPDFNumberTreeObjectHelper( |
| 1785 | pdf.getTrailer().getKey("/Bad1"), pdf); | 1787 | pdf.getTrailer().getKey("/Bad1"), pdf); |
| 1786 | assert(bad1.begin() == bad1.end()); | 1788 | assert(bad1.begin() == bad1.end()); |
| 1787 | assert(bad1.last() == bad1.end()); | 1789 | assert(bad1.last() == bad1.end()); |
| 1790 | + | ||
| 1791 | + std::cout << "/Bad2" << std::endl; | ||
| 1792 | + auto bad2 = QPDFNumberTreeObjectHelper( | ||
| 1793 | + pdf.getTrailer().getKey("/Bad2"), pdf); | ||
| 1794 | + for (auto i: bad2) | ||
| 1795 | + { | ||
| 1796 | + std::cout << i.first << " " << i.second.unparse() << std::endl; | ||
| 1797 | + } | ||
| 1798 | + | ||
| 1799 | + std::vector<std::string> empties = {"/Empty1", "/Empty2"}; | ||
| 1800 | + for (auto const& k: empties) | ||
| 1801 | + { | ||
| 1802 | + std::cout << k << std::endl; | ||
| 1803 | + auto empty = QPDFNumberTreeObjectHelper( | ||
| 1804 | + pdf.getTrailer().getKey(k), pdf); | ||
| 1805 | + assert(empty.begin() == empty.end()); | ||
| 1806 | + assert(empty.last() == empty.end()); | ||
| 1807 | + auto i = empty.insert(5, QPDFObjectHandle::newString("5")); | ||
| 1808 | + assert((*i).first == 5); | ||
| 1809 | + assert((*i).second.getStringValue() == "5"); | ||
| 1810 | + assert((*empty.begin()).first == 5); | ||
| 1811 | + assert((*empty.last()).first == 5); | ||
| 1812 | + assert((*empty.begin()).second.getStringValue() == "5"); | ||
| 1813 | + i = empty.insert(5, QPDFObjectHandle::newString("5+")); | ||
| 1814 | + assert((*i).first == 5); | ||
| 1815 | + assert((*i).second.getStringValue() == "5+"); | ||
| 1816 | + assert((*empty.begin()).second.getStringValue() == "5+"); | ||
| 1817 | + i = empty.insert(6, QPDFObjectHandle::newString("6")); | ||
| 1818 | + assert((*i).first == 6); | ||
| 1819 | + assert((*i).second.getStringValue() == "6"); | ||
| 1820 | + assert((*empty.begin()).second.getStringValue() == "5+"); | ||
| 1821 | + assert((*empty.last()).first == 6); | ||
| 1822 | + assert((*empty.last()).second.getStringValue() == "6"); | ||
| 1823 | + } | ||
| 1824 | + std::cout << "Insert into invalid" << std::endl; | ||
| 1825 | + auto invalid1 = QPDFNumberTreeObjectHelper( | ||
| 1826 | + QPDFObjectHandle::newDictionary(), pdf); | ||
| 1827 | + try | ||
| 1828 | + { | ||
| 1829 | + invalid1.insert(1, QPDFObjectHandle::newNull()); | ||
| 1830 | + } | ||
| 1831 | + catch (QPDFExc& e) | ||
| 1832 | + { | ||
| 1833 | + std::cout << e.what() << std::endl; | ||
| 1834 | + } | ||
| 1835 | + | ||
| 1836 | + std::cout << "/Bad3, no repair" << std::endl; | ||
| 1837 | + auto bad3_oh = pdf.getTrailer().getKey("/Bad3"); | ||
| 1838 | + auto bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, false); | ||
| 1839 | + for (auto i: bad3) | ||
| 1840 | + { | ||
| 1841 | + std::cout << i.first << " " << i.second.unparse() << std::endl; | ||
| 1842 | + } | ||
| 1843 | + assert(! bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect()); | ||
| 1844 | + | ||
| 1845 | + std::cout << "/Bad3, repair" << std::endl; | ||
| 1846 | + bad3 = QPDFNumberTreeObjectHelper(bad3_oh, pdf, true); | ||
| 1847 | + for (auto i: bad3) | ||
| 1848 | + { | ||
| 1849 | + std::cout << i.first << " " << i.second.unparse() << std::endl; | ||
| 1850 | + } | ||
| 1851 | + assert(bad3_oh.getKey("/Kids").getArrayItem(0).isIndirect()); | ||
| 1852 | + | ||
| 1853 | + std::cout << "/Bad4 -- missing limits" << std::endl; | ||
| 1854 | + auto bad4 = QPDFNumberTreeObjectHelper( | ||
| 1855 | + pdf.getTrailer().getKey("/Bad4"), pdf); | ||
| 1856 | + bad4.insert(5, QPDFObjectHandle::newString("5")); | ||
| 1857 | + for (auto i: bad4) | ||
| 1858 | + { | ||
| 1859 | + std::cout << i.first << " " << i.second.unparse() << std::endl; | ||
| 1860 | + } | ||
| 1861 | + | ||
| 1862 | + std::cout << "/Bad5 -- limit errors" << std::endl; | ||
| 1863 | + auto bad5 = QPDFNumberTreeObjectHelper( | ||
| 1864 | + pdf.getTrailer().getKey("/Bad5"), pdf); | ||
| 1865 | + assert(bad5.find(10) == bad5.end()); | ||
| 1788 | } | 1866 | } |
| 1789 | else if (n == 47) | 1867 | else if (n == 47) |
| 1790 | { | 1868 | { |
| @@ -1830,6 +1908,88 @@ void runtest(int n, char const* filename1, char const* arg2) | @@ -1830,6 +1908,88 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 1830 | auto last = ntoh.last(); | 1908 | auto last = ntoh.last(); |
| 1831 | assert((*last).first == "29 twenty-nine"); | 1909 | assert((*last).first == "29 twenty-nine"); |
| 1832 | assert((*last).second.getUTF8Value() == "twenty-nine!"); | 1910 | assert((*last).second.getUTF8Value() == "twenty-nine!"); |
| 1911 | + | ||
| 1912 | + std::vector<std::string> empties = {"/Empty1", "/Empty2"}; | ||
| 1913 | + for (auto const& k: empties) | ||
| 1914 | + { | ||
| 1915 | + std::cout << k << std::endl; | ||
| 1916 | + auto empty = QPDFNameTreeObjectHelper( | ||
| 1917 | + pdf.getTrailer().getKey(k), pdf); | ||
| 1918 | + assert(empty.begin() == empty.end()); | ||
| 1919 | + assert(empty.last() == empty.end()); | ||
| 1920 | + auto i = empty.insert("five", QPDFObjectHandle::newString("5")); | ||
| 1921 | + assert((*i).first == "five"); | ||
| 1922 | + assert((*i).second.getStringValue() == "5"); | ||
| 1923 | + assert((*empty.begin()).first == "five"); | ||
| 1924 | + assert((*empty.last()).first == "five"); | ||
| 1925 | + assert((*empty.begin()).second.getStringValue() == "5"); | ||
| 1926 | + i = empty.insert("five", QPDFObjectHandle::newString("5+")); | ||
| 1927 | + assert((*i).first == "five"); | ||
| 1928 | + assert((*i).second.getStringValue() == "5+"); | ||
| 1929 | + assert((*empty.begin()).second.getStringValue() == "5+"); | ||
| 1930 | + i = empty.insert("six", QPDFObjectHandle::newString("6")); | ||
| 1931 | + assert((*i).first == "six"); | ||
| 1932 | + assert((*i).second.getStringValue() == "6"); | ||
| 1933 | + assert((*empty.begin()).second.getStringValue() == "5+"); | ||
| 1934 | + assert((*empty.last()).first == "six"); | ||
| 1935 | + assert((*empty.last()).second.getStringValue() == "6"); | ||
| 1936 | + } | ||
| 1937 | + | ||
| 1938 | + // Exercise deprecated API until qpdf 11 | ||
| 1939 | + std::cout << "/Bad1: deprecated API" << std::endl; | ||
| 1940 | + auto bad1 = QPDFNameTreeObjectHelper( | ||
| 1941 | + pdf.getTrailer().getKey("/Bad1")); | ||
| 1942 | + try | ||
| 1943 | + { | ||
| 1944 | + bad1.find("G", true); | ||
| 1945 | + assert(false); | ||
| 1946 | + } | ||
| 1947 | + catch (std::runtime_error& e) | ||
| 1948 | + { | ||
| 1949 | + std::cout << e.what() << std::endl; | ||
| 1950 | + } | ||
| 1951 | + | ||
| 1952 | + std::cout << "/Bad1 -- wrong key type" << std::endl; | ||
| 1953 | + bad1 = QPDFNameTreeObjectHelper( | ||
| 1954 | + pdf.getTrailer().getKey("/Bad1"), pdf); | ||
| 1955 | + assert((*bad1.find("G", true)).first == "A"); | ||
| 1956 | + for (auto i: bad1) | ||
| 1957 | + { | ||
| 1958 | + std::cout << i.first << std::endl; | ||
| 1959 | + } | ||
| 1960 | + | ||
| 1961 | + std::cout << "/Bad2 -- invalid kid" << std::endl; | ||
| 1962 | + auto bad2 = QPDFNameTreeObjectHelper( | ||
| 1963 | + pdf.getTrailer().getKey("/Bad2"), pdf); | ||
| 1964 | + assert((*bad2.find("G", true)).first == "B"); | ||
| 1965 | + for (auto i: bad2) | ||
| 1966 | + { | ||
| 1967 | + std::cout << i.first << std::endl; | ||
| 1968 | + } | ||
| 1969 | + | ||
| 1970 | + std::cout << "/Bad3 -- invalid kid" << std::endl; | ||
| 1971 | + auto bad3 = QPDFNameTreeObjectHelper( | ||
| 1972 | + pdf.getTrailer().getKey("/Bad3"), pdf); | ||
| 1973 | + assert(bad3.find("G", true) == bad3.end()); | ||
| 1974 | + | ||
| 1975 | + std::cout << "/Bad4 -- invalid kid" << std::endl; | ||
| 1976 | + auto bad4 = QPDFNameTreeObjectHelper( | ||
| 1977 | + pdf.getTrailer().getKey("/Bad4"), pdf); | ||
| 1978 | + assert((*bad4.find("F", true)).first == "C"); | ||
| 1979 | + for (auto i: bad4) | ||
| 1980 | + { | ||
| 1981 | + std::cout << i.first << std::endl; | ||
| 1982 | + } | ||
| 1983 | + | ||
| 1984 | + std::cout << "/Bad5 -- loop in find" << std::endl; | ||
| 1985 | + auto bad5 = QPDFNameTreeObjectHelper( | ||
| 1986 | + pdf.getTrailer().getKey("/Bad5"), pdf); | ||
| 1987 | + assert((*bad5.find("F", true)).first == "D"); | ||
| 1988 | + | ||
| 1989 | + std::cout << "/Bad6 -- bad limits" << std::endl; | ||
| 1990 | + auto bad6 = QPDFNameTreeObjectHelper( | ||
| 1991 | + pdf.getTrailer().getKey("/Bad6"), pdf); | ||
| 1992 | + assert((*bad6.insert("H", QPDFObjectHandle::newNull())).first == "H"); | ||
| 1833 | } | 1993 | } |
| 1834 | else if (n == 49) | 1994 | else if (n == 49) |
| 1835 | { | 1995 | { |
| @@ -2326,6 +2486,57 @@ void runtest(int n, char const* filename1, char const* arg2) | @@ -2326,6 +2486,57 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 2326 | pdf.closeInputSource(); | 2486 | pdf.closeInputSource(); |
| 2327 | pdf.getRoot().getKey("/Pages").unparseResolved(); | 2487 | pdf.getRoot().getKey("/Pages").unparseResolved(); |
| 2328 | } | 2488 | } |
| 2489 | + else if (n == 74) | ||
| 2490 | + { | ||
| 2491 | + // This test is crafted to work with split-nntree.pdf | ||
| 2492 | + std::cout << "/Split1" << std::endl; | ||
| 2493 | + auto split1 = QPDFNumberTreeObjectHelper( | ||
| 2494 | + pdf.getTrailer().getKey("/Split1"), pdf); | ||
| 2495 | + split1.setSplitThreshold(4); | ||
| 2496 | + auto check_split1 = [&split1](int k) { | ||
| 2497 | + auto i = split1.insert(k, QPDFObjectHandle::newString( | ||
| 2498 | + QUtil::int_to_string(k))); | ||
| 2499 | + assert((*i).first == k); | ||
| 2500 | + }; | ||
| 2501 | + check_split1(15); | ||
| 2502 | + check_split1(35); | ||
| 2503 | + check_split1(125); | ||
| 2504 | + for (auto i: split1) | ||
| 2505 | + { | ||
| 2506 | + std::cout << i.first << std::endl; | ||
| 2507 | + } | ||
| 2508 | + | ||
| 2509 | + std::cout << "/Split2" << std::endl; | ||
| 2510 | + auto split2 = QPDFNameTreeObjectHelper( | ||
| 2511 | + pdf.getTrailer().getKey("/Split2"), pdf); | ||
| 2512 | + split2.setSplitThreshold(4); | ||
| 2513 | + auto check_split2 = [](QPDFNameTreeObjectHelper& noh, | ||
| 2514 | + std::string const& k) { | ||
| 2515 | + auto i = noh.insert(k, QPDFObjectHandle::newUnicodeString(k)); | ||
| 2516 | + assert((*i).first == k); | ||
| 2517 | + }; | ||
| 2518 | + check_split2(split2, "C"); | ||
| 2519 | + for (auto i: split2) | ||
| 2520 | + { | ||
| 2521 | + std::cout << i.first << std::endl; | ||
| 2522 | + } | ||
| 2523 | + | ||
| 2524 | + std::cout << "/Split3" << std::endl; | ||
| 2525 | + auto split3 = QPDFNameTreeObjectHelper( | ||
| 2526 | + pdf.getTrailer().getKey("/Split3"), pdf); | ||
| 2527 | + split3.setSplitThreshold(4); | ||
| 2528 | + check_split2(split3, "P"); | ||
| 2529 | + check_split2(split3, "\xcf\x80"); | ||
| 2530 | + for (auto i: split3) | ||
| 2531 | + { | ||
| 2532 | + std::cout << i.first << " " << i.second.unparse() << std::endl; | ||
| 2533 | + } | ||
| 2534 | + | ||
| 2535 | + QPDFWriter w(pdf, "a.pdf"); | ||
| 2536 | + w.setStaticID(true); | ||
| 2537 | + w.setQDFMode(true); | ||
| 2538 | + w.write(); | ||
| 2539 | + } | ||
| 2329 | else | 2540 | else |
| 2330 | { | 2541 | { |
| 2331 | throw std::runtime_error(std::string("invalid test ") + | 2542 | throw std::runtime_error(std::string("invalid test ") + |