/src/qpdf/libqpdf/QPDF_Array.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include <qpdf/QPDFObjectHandle_private.hh> |
2 | | |
3 | | #include <qpdf/QTC.hh> |
4 | | |
5 | | using namespace std::literals; |
6 | | using namespace qpdf; |
7 | | |
8 | | static const QPDFObjectHandle null_oh = QPDFObjectHandle::newNull(); |
9 | | |
10 | | inline void |
11 | | Array::checkOwnership(QPDFObjectHandle const& item) const |
12 | 0 | { |
13 | 0 | if (!item) { |
14 | 0 | throw std::logic_error("Attempting to add an uninitialized object to a QPDF_Array."); |
15 | 0 | } |
16 | 0 | if (qpdf() && item.qpdf() && qpdf() != item.qpdf()) { |
17 | 0 | throw std::logic_error( |
18 | 0 | "Attempting to add an object from a different QPDF. Use " |
19 | 0 | "QPDF::copyForeignObject to add objects from another file."); |
20 | 0 | } |
21 | 0 | } |
22 | | |
23 | | QPDF_Array::QPDF_Array(std::vector<QPDFObjectHandle>&& v, bool sparse) |
24 | 0 | { |
25 | 0 | if (sparse) { |
26 | 0 | sp = std::make_unique<Sparse>(); |
27 | 0 | for (auto& item: v) { |
28 | 0 | if (item.raw_type_code() != ::ot_null || item.indirect()) { |
29 | 0 | sp->elements[sp->size] = std::move(item); |
30 | 0 | } |
31 | 0 | ++sp->size; |
32 | 0 | } |
33 | 0 | } else { |
34 | 0 | elements = std::move(v); |
35 | 0 | } |
36 | 0 | } |
37 | | |
38 | | QPDF_Array* |
39 | | Array::array() const |
40 | 0 | { |
41 | 0 | if (auto a = as<QPDF_Array>()) { |
42 | 0 | return a; |
43 | 0 | } |
44 | | |
45 | 0 | throw std::runtime_error("Expected an array but found a non-array object"); |
46 | 0 | return nullptr; // unreachable |
47 | 0 | } |
48 | | |
49 | | Array::iterator |
50 | | Array::begin() |
51 | 0 | { |
52 | 0 | if (auto a = as<QPDF_Array>()) { |
53 | 0 | if (!a->sp) { |
54 | 0 | return a->elements.begin(); |
55 | 0 | } |
56 | 0 | if (!sp_elements) { |
57 | 0 | sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector()); |
58 | 0 | } |
59 | 0 | return sp_elements->begin(); |
60 | 0 | } |
61 | 0 | return {}; |
62 | 0 | } |
63 | | |
64 | | Array::iterator |
65 | | Array::end() |
66 | 0 | { |
67 | 0 | if (auto a = as<QPDF_Array>()) { |
68 | 0 | if (!a->sp) { |
69 | 0 | return a->elements.end(); |
70 | 0 | } |
71 | 0 | if (!sp_elements) { |
72 | 0 | sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector()); |
73 | 0 | } |
74 | 0 | return sp_elements->end(); |
75 | 0 | } |
76 | 0 | return {}; |
77 | 0 | } |
78 | | |
79 | | Array::const_iterator |
80 | | Array::cbegin() |
81 | 0 | { |
82 | 0 | if (auto a = as<QPDF_Array>()) { |
83 | 0 | if (!a->sp) { |
84 | 0 | return a->elements.cbegin(); |
85 | 0 | } |
86 | 0 | if (!sp_elements) { |
87 | 0 | sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector()); |
88 | 0 | } |
89 | 0 | return sp_elements->cbegin(); |
90 | 0 | } |
91 | 0 | return {}; |
92 | 0 | } |
93 | | |
94 | | Array::const_iterator |
95 | | Array::cend() |
96 | 0 | { |
97 | 0 | if (auto a = as<QPDF_Array>()) { |
98 | 0 | if (!a->sp) { |
99 | 0 | return a->elements.cend(); |
100 | 0 | } |
101 | 0 | if (!sp_elements) { |
102 | 0 | sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector()); |
103 | 0 | } |
104 | 0 | return sp_elements->cend(); |
105 | 0 | } |
106 | 0 | return {}; |
107 | 0 | } |
108 | | |
109 | | Array::const_reverse_iterator |
110 | | Array::crbegin() |
111 | 0 | { |
112 | 0 | if (auto a = as<QPDF_Array>()) { |
113 | 0 | if (!a->sp) { |
114 | 0 | return a->elements.crbegin(); |
115 | 0 | } |
116 | 0 | if (!sp_elements) { |
117 | 0 | sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector()); |
118 | 0 | } |
119 | 0 | return sp_elements->crbegin(); |
120 | 0 | } |
121 | 0 | return {}; |
122 | 0 | } |
123 | | |
124 | | Array::const_reverse_iterator |
125 | | Array::crend() |
126 | 0 | { |
127 | 0 | if (auto a = as<QPDF_Array>()) { |
128 | 0 | if (!a->sp) { |
129 | 0 | return a->elements.crend(); |
130 | 0 | } |
131 | 0 | if (!sp_elements) { |
132 | 0 | sp_elements = std::make_unique<std::vector<QPDFObjectHandle>>(getAsVector()); |
133 | 0 | } |
134 | 0 | return sp_elements->crend(); |
135 | 0 | } |
136 | 0 | return {}; |
137 | 0 | } |
138 | | |
139 | | QPDFObjectHandle |
140 | | Array::null() const |
141 | 0 | { |
142 | 0 | return null_oh; |
143 | 0 | } |
144 | | |
145 | | int |
146 | | Array::size() const |
147 | 0 | { |
148 | 0 | auto a = array(); |
149 | 0 | return a->sp ? a->sp->size : int(a->elements.size()); |
150 | 0 | } |
151 | | |
152 | | std::pair<bool, QPDFObjectHandle> |
153 | | Array::at(int n) const |
154 | 0 | { |
155 | 0 | auto a = array(); |
156 | 0 | if (n < 0 || n >= size()) { |
157 | 0 | return {false, {}}; |
158 | 0 | } |
159 | 0 | if (!a->sp) { |
160 | 0 | return {true, a->elements[size_t(n)]}; |
161 | 0 | } |
162 | 0 | auto const& iter = a->sp->elements.find(n); |
163 | 0 | return {true, iter == a->sp->elements.end() ? null() : iter->second}; |
164 | 0 | } |
165 | | |
166 | | std::vector<QPDFObjectHandle> |
167 | | Array::getAsVector() const |
168 | 0 | { |
169 | 0 | auto a = array(); |
170 | 0 | if (a->sp) { |
171 | 0 | std::vector<QPDFObjectHandle> v; |
172 | 0 | v.reserve(size_t(size())); |
173 | 0 | for (auto const& item: a->sp->elements) { |
174 | 0 | v.resize(size_t(item.first), null_oh); |
175 | 0 | v.emplace_back(item.second); |
176 | 0 | } |
177 | 0 | v.resize(size_t(size()), null_oh); |
178 | 0 | return v; |
179 | 0 | } else { |
180 | 0 | return a->elements; |
181 | 0 | } |
182 | 0 | } |
183 | | |
184 | | bool |
185 | | Array::setAt(int at, QPDFObjectHandle const& oh) |
186 | 0 | { |
187 | 0 | if (at < 0 || at >= size()) { |
188 | 0 | return false; |
189 | 0 | } |
190 | 0 | auto a = array(); |
191 | 0 | checkOwnership(oh); |
192 | 0 | if (a->sp) { |
193 | 0 | a->sp->elements[at] = oh; |
194 | 0 | } else { |
195 | 0 | a->elements[size_t(at)] = oh; |
196 | 0 | } |
197 | 0 | return true; |
198 | 0 | } |
199 | | |
200 | | void |
201 | | Array::setFromVector(std::vector<QPDFObjectHandle> const& v) |
202 | 0 | { |
203 | 0 | auto a = array(); |
204 | 0 | a->elements.resize(0); |
205 | 0 | a->elements.reserve(v.size()); |
206 | 0 | for (auto const& item: v) { |
207 | 0 | checkOwnership(item); |
208 | 0 | a->elements.emplace_back(item); |
209 | 0 | } |
210 | 0 | } |
211 | | |
212 | | bool |
213 | | Array::insert(int at, QPDFObjectHandle const& item) |
214 | 0 | { |
215 | 0 | auto a = array(); |
216 | 0 | int sz = size(); |
217 | 0 | if (at < 0 || at > sz) { |
218 | | // As special case, also allow insert beyond the end |
219 | 0 | return false; |
220 | 0 | } else if (at == sz) { |
221 | 0 | push_back(item); |
222 | 0 | } else { |
223 | 0 | checkOwnership(item); |
224 | 0 | if (a->sp) { |
225 | 0 | auto iter = a->sp->elements.crbegin(); |
226 | 0 | while (iter != a->sp->elements.crend()) { |
227 | 0 | auto key = (iter++)->first; |
228 | 0 | if (key >= at) { |
229 | 0 | auto nh = a->sp->elements.extract(key); |
230 | 0 | ++nh.key(); |
231 | 0 | a->sp->elements.insert(std::move(nh)); |
232 | 0 | } else { |
233 | 0 | break; |
234 | 0 | } |
235 | 0 | } |
236 | 0 | a->sp->elements[at] = item.getObj(); |
237 | 0 | ++a->sp->size; |
238 | 0 | } else { |
239 | 0 | a->elements.insert(a->elements.cbegin() + at, item.getObj()); |
240 | 0 | } |
241 | 0 | } |
242 | 0 | return true; |
243 | 0 | } |
244 | | |
245 | | void |
246 | | Array::push_back(QPDFObjectHandle const& item) |
247 | 0 | { |
248 | 0 | auto a = array(); |
249 | 0 | checkOwnership(item); |
250 | 0 | if (a->sp) { |
251 | 0 | a->sp->elements[(a->sp->size)++] = item; |
252 | 0 | } else { |
253 | 0 | a->elements.emplace_back(item); |
254 | 0 | } |
255 | 0 | } |
256 | | |
257 | | bool |
258 | | Array::erase(int at) |
259 | 0 | { |
260 | 0 | auto a = array(); |
261 | 0 | if (at < 0 || at >= size()) { |
262 | 0 | return false; |
263 | 0 | } |
264 | 0 | if (a->sp) { |
265 | 0 | auto end = a->sp->elements.end(); |
266 | 0 | if (auto iter = a->sp->elements.lower_bound(at); iter != end) { |
267 | 0 | if (iter->first == at) { |
268 | 0 | iter++; |
269 | 0 | a->sp->elements.erase(at); |
270 | 0 | } |
271 | |
|
272 | 0 | while (iter != end) { |
273 | 0 | auto nh = a->sp->elements.extract(iter++); |
274 | 0 | --nh.key(); |
275 | 0 | a->sp->elements.insert(std::move(nh)); |
276 | 0 | } |
277 | 0 | } |
278 | 0 | --(a->sp->size); |
279 | 0 | } else { |
280 | 0 | a->elements.erase(a->elements.cbegin() + at); |
281 | 0 | } |
282 | 0 | return true; |
283 | 0 | } |
284 | | |
285 | | int |
286 | | QPDFObjectHandle::getArrayNItems() const |
287 | 0 | { |
288 | 0 | if (auto array = as_array(strict)) { |
289 | 0 | return array.size(); |
290 | 0 | } |
291 | 0 | typeWarning("array", "treating as empty"); |
292 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); |
293 | 0 | return 0; |
294 | 0 | } |
295 | | |
296 | | QPDFObjectHandle |
297 | | QPDFObjectHandle::getArrayItem(int n) const |
298 | 0 | { |
299 | 0 | if (auto array = as_array(strict)) { |
300 | 0 | if (auto const [success, oh] = array.at(n); success) { |
301 | 0 | return oh; |
302 | 0 | } else { |
303 | 0 | objectWarning("returning null for out of bounds array access"); |
304 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array bounds"); |
305 | 0 | } |
306 | 0 | } else { |
307 | 0 | typeWarning("array", "returning null"); |
308 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array null for non-array"); |
309 | 0 | } |
310 | 0 | static auto constexpr msg = " -> null returned from invalid array access"sv; |
311 | 0 | return QPDF_Null::create(obj, msg, ""); |
312 | 0 | } |
313 | | |
314 | | bool |
315 | | QPDFObjectHandle::isRectangle() const |
316 | 0 | { |
317 | 0 | if (auto array = as_array(strict)) { |
318 | 0 | for (int i = 0; i < 4; ++i) { |
319 | 0 | if (auto item = array.at(i).second; !item.isNumber()) { |
320 | 0 | return false; |
321 | 0 | } |
322 | 0 | } |
323 | 0 | return array.size() == 4; |
324 | 0 | } |
325 | 0 | return false; |
326 | 0 | } |
327 | | |
328 | | bool |
329 | | QPDFObjectHandle::isMatrix() const |
330 | 0 | { |
331 | 0 | if (auto array = as_array(strict)) { |
332 | 0 | for (int i = 0; i < 6; ++i) { |
333 | 0 | if (auto item = array.at(i).second; !item.isNumber()) { |
334 | 0 | return false; |
335 | 0 | } |
336 | 0 | } |
337 | 0 | return array.size() == 6; |
338 | 0 | } |
339 | 0 | return false; |
340 | 0 | } |
341 | | |
342 | | QPDFObjectHandle::Rectangle |
343 | | QPDFObjectHandle::getArrayAsRectangle() const |
344 | 0 | { |
345 | 0 | if (auto array = as_array(strict)) { |
346 | 0 | if (array.size() != 4) { |
347 | 0 | return {}; |
348 | 0 | } |
349 | 0 | double items[4]; |
350 | 0 | for (int i = 0; i < 4; ++i) { |
351 | 0 | if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) { |
352 | 0 | return {}; |
353 | 0 | } |
354 | 0 | } |
355 | 0 | return { |
356 | 0 | std::min(items[0], items[2]), |
357 | 0 | std::min(items[1], items[3]), |
358 | 0 | std::max(items[0], items[2]), |
359 | 0 | std::max(items[1], items[3])}; |
360 | 0 | } |
361 | 0 | return {}; |
362 | 0 | } |
363 | | |
364 | | QPDFObjectHandle::Matrix |
365 | | QPDFObjectHandle::getArrayAsMatrix() const |
366 | 0 | { |
367 | 0 | if (auto array = as_array(strict)) { |
368 | 0 | if (array.size() != 6) { |
369 | 0 | return {}; |
370 | 0 | } |
371 | 0 | double items[6]; |
372 | 0 | for (int i = 0; i < 6; ++i) { |
373 | 0 | if (auto item = array.at(i).second; !item.getValueAsNumber(items[i])) { |
374 | 0 | return {}; |
375 | 0 | } |
376 | 0 | } |
377 | 0 | return {items[0], items[1], items[2], items[3], items[4], items[5]}; |
378 | 0 | } |
379 | 0 | return {}; |
380 | 0 | } |
381 | | |
382 | | std::vector<QPDFObjectHandle> |
383 | | QPDFObjectHandle::getArrayAsVector() const |
384 | 0 | { |
385 | 0 | if (auto array = as_array(strict)) { |
386 | 0 | return array.getAsVector(); |
387 | 0 | } |
388 | 0 | typeWarning("array", "treating as empty"); |
389 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); |
390 | 0 | return {}; |
391 | 0 | } |
392 | | |
393 | | void |
394 | | QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) |
395 | 0 | { |
396 | 0 | if (auto array = as_array(strict)) { |
397 | 0 | if (!array.setAt(n, item)) { |
398 | 0 | objectWarning("ignoring attempt to set out of bounds array item"); |
399 | 0 | QTC::TC("qpdf", "QPDFObjectHandle set array bounds"); |
400 | 0 | } |
401 | 0 | } else { |
402 | 0 | typeWarning("array", "ignoring attempt to set item"); |
403 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); |
404 | 0 | } |
405 | 0 | } |
406 | | void |
407 | | QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items) |
408 | 0 | { |
409 | 0 | if (auto array = as_array(strict)) { |
410 | 0 | array.setFromVector(items); |
411 | 0 | } else { |
412 | 0 | typeWarning("array", "ignoring attempt to replace items"); |
413 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items"); |
414 | 0 | } |
415 | 0 | } |
416 | | |
417 | | void |
418 | | QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item) |
419 | 0 | { |
420 | 0 | if (auto array = as_array(strict)) { |
421 | 0 | if (!array.insert(at, item)) { |
422 | 0 | objectWarning("ignoring attempt to insert out of bounds array item"); |
423 | 0 | QTC::TC("qpdf", "QPDFObjectHandle insert array bounds"); |
424 | 0 | } |
425 | 0 | } else { |
426 | 0 | typeWarning("array", "ignoring attempt to insert item"); |
427 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | QPDFObjectHandle |
432 | | QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item) |
433 | 0 | { |
434 | 0 | insertItem(at, item); |
435 | 0 | return item; |
436 | 0 | } |
437 | | |
438 | | void |
439 | | QPDFObjectHandle::appendItem(QPDFObjectHandle const& item) |
440 | 0 | { |
441 | 0 | if (auto array = as_array(strict)) { |
442 | 0 | array.push_back(item); |
443 | 0 | } else { |
444 | 0 | typeWarning("array", "ignoring attempt to append item"); |
445 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); |
446 | 0 | } |
447 | 0 | } |
448 | | |
449 | | QPDFObjectHandle |
450 | | QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item) |
451 | 0 | { |
452 | 0 | appendItem(item); |
453 | 0 | return item; |
454 | 0 | } |
455 | | |
456 | | void |
457 | | QPDFObjectHandle::eraseItem(int at) |
458 | 0 | { |
459 | 0 | if (auto array = as_array(strict)) { |
460 | 0 | if (!array.erase(at)) { |
461 | 0 | objectWarning("ignoring attempt to erase out of bounds array item"); |
462 | 0 | QTC::TC("qpdf", "QPDFObjectHandle erase array bounds"); |
463 | 0 | } |
464 | 0 | } else { |
465 | 0 | typeWarning("array", "ignoring attempt to erase item"); |
466 | 0 | QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item"); |
467 | 0 | } |
468 | 0 | } |
469 | | |
470 | | QPDFObjectHandle |
471 | | QPDFObjectHandle::eraseItemAndGetOld(int at) |
472 | 0 | { |
473 | 0 | auto array = as_array(strict); |
474 | 0 | auto result = (array && at < array.size() && at >= 0) ? array.at(at).second : newNull(); |
475 | 0 | eraseItem(at); |
476 | 0 | return result; |
477 | 0 | } |