/src/gdal/frmts/pdf/pdfreadvectors.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: PDF driver |
4 | | * Purpose: GDALDataset driver for PDF dataset (read vector features) |
5 | | * Author: Even Rouault, <even dot rouault at spatialys.com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "gdal_pdf.h" |
14 | | |
15 | | #include <algorithm> |
16 | | #include <array> |
17 | | |
18 | 181k | #define SQUARE(x) ((x) * (x)) |
19 | 112k | #define EPSILON 1e-5 |
20 | | |
21 | | // #define DEBUG_VERBOSE |
22 | | |
23 | | #ifdef HAVE_PDF_READ_SUPPORT |
24 | | |
25 | | constexpr int BEZIER_STEPS = 10; |
26 | | |
27 | | /************************************************************************/ |
28 | | /* OpenVectorLayers() */ |
29 | | /************************************************************************/ |
30 | | |
31 | | bool PDFDataset::OpenVectorLayers(GDALPDFDictionary *poPageDict) |
32 | 111M | { |
33 | 111M | if (m_bHasLoadedLayers) |
34 | 111M | return true; |
35 | 17.1k | m_bHasLoadedLayers = true; |
36 | | |
37 | 17.1k | if (poPageDict == nullptr) |
38 | 8.56k | { |
39 | 8.56k | poPageDict = m_poPageObj->GetDictionary(); |
40 | 8.56k | if (poPageDict == nullptr) |
41 | 0 | return false; |
42 | 8.56k | } |
43 | | |
44 | 17.1k | GetCatalog(); |
45 | 17.1k | if (m_poCatalogObject == nullptr || |
46 | 17.1k | m_poCatalogObject->GetType() != PDFObjectType_Dictionary) |
47 | 0 | return false; |
48 | | |
49 | 17.1k | GDALPDFObject *poContents = poPageDict->Get("Contents"); |
50 | 17.1k | if (poContents == nullptr) |
51 | 4.18k | return false; |
52 | | |
53 | 12.9k | if (poContents->GetType() != PDFObjectType_Dictionary && |
54 | 1.22k | poContents->GetType() != PDFObjectType_Array) |
55 | 160 | return false; |
56 | | |
57 | 12.8k | GDALPDFObject *poResources = poPageDict->Get("Resources"); |
58 | 12.8k | if (poResources == nullptr || |
59 | 11.3k | poResources->GetType() != PDFObjectType_Dictionary) |
60 | 1.52k | return false; |
61 | | |
62 | 11.2k | GDALPDFObject *poStructTreeRoot = |
63 | 11.2k | m_poCatalogObject->GetDictionary()->Get("StructTreeRoot"); |
64 | 11.2k | if (CPLTestBool(CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", "NO")) || |
65 | 11.2k | poStructTreeRoot == nullptr || |
66 | 2.33k | poStructTreeRoot->GetType() != PDFObjectType_Dictionary) |
67 | 8.99k | { |
68 | 8.99k | ExploreContentsNonStructured(poContents, poResources); |
69 | 8.99k | } |
70 | 2.28k | else |
71 | 2.28k | { |
72 | 2.28k | bool bHasFeatures; |
73 | 2.28k | { |
74 | 2.28k | std::set<std::pair<int, int>> aoSetAlreadyVisited; |
75 | 2.28k | bHasFeatures = ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0, |
76 | 2.28k | /* bDryRun = */ true); |
77 | 2.28k | } |
78 | 2.28k | if (bHasFeatures) |
79 | 1.48k | { |
80 | 1.48k | int nDepth = 0; |
81 | 1.48k | int nVisited = 0; |
82 | 1.48k | bool bStop = false; |
83 | 1.48k | ExploreContents(poContents, poResources, nDepth, nVisited, bStop); |
84 | 1.48k | std::set<std::pair<int, int>> aoSetAlreadyVisited; |
85 | 1.48k | ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0, |
86 | 1.48k | /* bDryRun = */ false); |
87 | 1.48k | } |
88 | 801 | else |
89 | 801 | { |
90 | 801 | ExploreContentsNonStructured(poContents, poResources); |
91 | 801 | } |
92 | 2.28k | } |
93 | | |
94 | 11.2k | CleanupIntermediateResources(); |
95 | | |
96 | 11.2k | bool bEmptyDS = true; |
97 | 11.2k | for (auto &poLayer : m_apoLayers) |
98 | 1.78k | { |
99 | 1.78k | if (poLayer->GetFeatureCount(false) != 0) |
100 | 1.78k | { |
101 | 1.78k | bEmptyDS = false; |
102 | 1.78k | break; |
103 | 1.78k | } |
104 | 1.78k | } |
105 | 11.2k | return !bEmptyDS; |
106 | 12.8k | } |
107 | | |
108 | | /************************************************************************/ |
109 | | /* CleanupIntermediateResources() */ |
110 | | /************************************************************************/ |
111 | | |
112 | | void PDFDataset::CleanupIntermediateResources() |
113 | 49.3k | { |
114 | 49.3k | m_oMapMCID.clear(); |
115 | 49.3k | } |
116 | | |
117 | | /************************************************************************/ |
118 | | /* InitMapOperators() */ |
119 | | /************************************************************************/ |
120 | | |
121 | | typedef struct |
122 | | { |
123 | | char szOpName[4]; |
124 | | int nArgs; |
125 | | } PDFOperator; |
126 | | |
127 | | static const PDFOperator asPDFOperators[] = { |
128 | | {"b", 0}, |
129 | | {"B", 0}, |
130 | | {"b*", 0}, |
131 | | {"B*", 0}, |
132 | | {"BDC", 2}, |
133 | | // BI |
134 | | {"BMC", 1}, |
135 | | // BT |
136 | | {"BX", 0}, |
137 | | {"c", 6}, |
138 | | {"cm", 6}, |
139 | | {"CS", 1}, |
140 | | {"cs", 1}, |
141 | | {"d", 1}, /* we have ignored the first arg which is an array */ |
142 | | // d0 |
143 | | // d1 |
144 | | {"Do", 1}, |
145 | | {"DP", 2}, |
146 | | // EI |
147 | | {"EMC", 0}, |
148 | | // ET |
149 | | {"EX", 0}, |
150 | | {"f", 0}, |
151 | | {"F", 0}, |
152 | | {"f*", 0}, |
153 | | {"G", 1}, |
154 | | {"g", 1}, |
155 | | {"gs", 1}, |
156 | | {"h", 0}, |
157 | | {"i", 1}, |
158 | | // ID |
159 | | {"j", 1}, |
160 | | {"J", 1}, |
161 | | {"K", 4}, |
162 | | {"k", 4}, |
163 | | {"l", 2}, |
164 | | {"m", 2}, |
165 | | {"M", 1}, |
166 | | {"MP", 1}, |
167 | | {"n", 0}, |
168 | | {"q", 0}, |
169 | | {"Q", 0}, |
170 | | {"re", 4}, |
171 | | {"RG", 3}, |
172 | | {"rg", 3}, |
173 | | {"ri", 1}, |
174 | | {"s", 0}, |
175 | | {"S", 0}, |
176 | | {"SC", -1}, |
177 | | {"sc", -1}, |
178 | | {"SCN", -1}, |
179 | | {"scn", -1}, |
180 | | {"sh", 1}, |
181 | | // T* |
182 | | {"Tc", 1}, |
183 | | {"Td", 2}, |
184 | | {"TD", 2}, |
185 | | {"Tf", 1}, |
186 | | {"Tj", 1}, |
187 | | {"TJ", 1}, |
188 | | {"TL", 1}, |
189 | | {"Tm", 6}, |
190 | | {"Tr", 1}, |
191 | | {"Ts", 1}, |
192 | | {"Tw", 1}, |
193 | | {"Tz", 1}, |
194 | | {"v", 4}, |
195 | | {"w", 1}, |
196 | | {"W", 0}, |
197 | | {"W*", 0}, |
198 | | {"y", 4}, |
199 | | // ' |
200 | | // " |
201 | | }; |
202 | | |
203 | | void PDFDataset::InitMapOperators() |
204 | 38.0k | { |
205 | 38.0k | for (const auto &sPDFOperator : asPDFOperators) |
206 | 2.39M | m_oMapOperators[sPDFOperator.szOpName] = sPDFOperator.nArgs; |
207 | 38.0k | } |
208 | | |
209 | | /************************************************************************/ |
210 | | /* TestCapability() */ |
211 | | /************************************************************************/ |
212 | | |
213 | | int PDFDataset::TestCapability(const char *) const |
214 | 0 | { |
215 | 0 | return FALSE; |
216 | 0 | } |
217 | | |
218 | | /************************************************************************/ |
219 | | /* GetLayer() */ |
220 | | /************************************************************************/ |
221 | | |
222 | | const OGRLayer *PDFDataset::GetLayer(int iLayer) const |
223 | | |
224 | 110M | { |
225 | 110M | const_cast<PDFDataset *>(this)->OpenVectorLayers(nullptr); |
226 | 110M | if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size())) |
227 | 0 | return nullptr; |
228 | | |
229 | 110M | return m_apoLayers[iLayer].get(); |
230 | 110M | } |
231 | | |
232 | | /************************************************************************/ |
233 | | /* GetLayerCount() */ |
234 | | /************************************************************************/ |
235 | | |
236 | | int PDFDataset::GetLayerCount() const |
237 | 1.30M | { |
238 | 1.30M | const_cast<PDFDataset *>(this)->OpenVectorLayers(nullptr); |
239 | 1.30M | return static_cast<int>(m_apoLayers.size()); |
240 | 1.30M | } |
241 | | |
242 | | /************************************************************************/ |
243 | | /* ExploreTree() */ |
244 | | /************************************************************************/ |
245 | | |
246 | | bool PDFDataset::ExploreTree(GDALPDFObject *poObj, |
247 | | std::set<std::pair<int, int>> &aoSetAlreadyVisited, |
248 | | int nRecLevel, bool bDryRun) |
249 | 72.3k | { |
250 | 72.3k | if (nRecLevel == 16) |
251 | 0 | return false; |
252 | | |
253 | 72.3k | std::pair<int, int> oObjPair(poObj->GetRefNum().toInt(), |
254 | 72.3k | poObj->GetRefGen()); |
255 | 72.3k | if (aoSetAlreadyVisited.find(oObjPair) != aoSetAlreadyVisited.end()) |
256 | 39.7k | return false; |
257 | 32.6k | aoSetAlreadyVisited.insert(oObjPair); |
258 | | |
259 | 32.6k | if (poObj->GetType() != PDFObjectType_Dictionary) |
260 | 636 | return false; |
261 | | |
262 | 32.0k | GDALPDFDictionary *poDict = poObj->GetDictionary(); |
263 | | |
264 | 32.0k | GDALPDFObject *poS = poDict->Get("S"); |
265 | 32.0k | std::string osS; |
266 | 32.0k | if (poS != nullptr && poS->GetType() == PDFObjectType_Name) |
267 | 27.7k | { |
268 | 27.7k | osS = poS->GetName(); |
269 | 27.7k | } |
270 | | |
271 | 32.0k | GDALPDFObject *poT = poDict->Get("T"); |
272 | 32.0k | std::string osT; |
273 | 32.0k | if (poT != nullptr && poT->GetType() == PDFObjectType_String) |
274 | 10.9k | { |
275 | 10.9k | osT = poT->GetString(); |
276 | 10.9k | } |
277 | | |
278 | 32.0k | GDALPDFObject *poK = poDict->Get("K"); |
279 | 32.0k | if (poK == nullptr) |
280 | 1.21k | return false; |
281 | | |
282 | 30.7k | bool bRet = false; |
283 | 30.7k | if (poK->GetType() == PDFObjectType_Array) |
284 | 21.3k | { |
285 | 21.3k | GDALPDFArray *poArray = poK->GetArray(); |
286 | 21.3k | GDALPDFObject *poElt0; |
287 | 21.3k | GDALPDFObject *poElt0_K; |
288 | 21.3k | if (poArray->GetLength() > 0 && (poElt0 = poArray->Get(0)) && |
289 | 14.1k | poElt0->GetType() == PDFObjectType_Dictionary && |
290 | 12.1k | (poElt0_K = poElt0->GetDictionary()->Get("K")) && |
291 | 11.0k | poElt0_K->GetType() == PDFObjectType_Int) |
292 | 4.57k | { |
293 | 4.57k | if (bDryRun) |
294 | 2.43k | { |
295 | 5.33k | for (int i = 0; i < poArray->GetLength(); i++) |
296 | 4.38k | { |
297 | 4.38k | auto poFeatureObj = poArray->Get(i); |
298 | 4.38k | if (poFeatureObj && |
299 | 4.37k | poFeatureObj->GetType() == PDFObjectType_Dictionary) |
300 | 4.15k | { |
301 | 4.15k | auto poA = poFeatureObj->GetDictionary()->Get("A"); |
302 | 4.15k | if (poA && poA->GetType() == PDFObjectType_Dictionary) |
303 | 2.03k | { |
304 | 2.03k | auto poO = poA->GetDictionary()->Get("O"); |
305 | 2.03k | if (poO && poO->GetType() == PDFObjectType_Name && |
306 | 2.02k | poO->GetName() == "UserProperties") |
307 | 1.48k | { |
308 | 1.48k | return true; |
309 | 1.48k | } |
310 | 2.03k | } |
311 | 4.15k | } |
312 | 4.38k | } |
313 | 948 | return false; |
314 | 2.43k | } |
315 | | |
316 | 2.14k | std::string osLayerName; |
317 | 2.14k | if (!osT.empty()) |
318 | 2.09k | osLayerName = std::move(osT); |
319 | 46 | else |
320 | 46 | { |
321 | 46 | if (!osS.empty()) |
322 | 9 | osLayerName = std::move(osS); |
323 | 37 | else |
324 | 37 | osLayerName = CPLSPrintf( |
325 | 37 | "Layer%d", static_cast<int>(m_apoLayers.size()) + 1); |
326 | 46 | } |
327 | | |
328 | 2.14k | auto poSRSOri = GetSpatialRef(); |
329 | 2.14k | OGRSpatialReference *poSRS = poSRSOri ? poSRSOri->Clone() : nullptr; |
330 | 2.14k | auto poLayer = std::make_unique<OGRPDFLayer>( |
331 | 2.14k | this, osLayerName.c_str(), poSRS, wkbUnknown); |
332 | 2.14k | if (poSRS) |
333 | 550 | poSRS->Release(); |
334 | | |
335 | 2.14k | poLayer->Fill(poArray); |
336 | | |
337 | 2.14k | m_apoLayers.emplace_back(std::move(poLayer)); |
338 | 2.14k | bRet = true; |
339 | 2.14k | } |
340 | 16.7k | else |
341 | 16.7k | { |
342 | 79.6k | for (int i = 0; i < poArray->GetLength(); i++) |
343 | 64.3k | { |
344 | 64.3k | auto poSubObj = poArray->Get(i); |
345 | 64.3k | if (poSubObj) |
346 | 62.5k | { |
347 | 62.5k | if (ExploreTree(poSubObj, aoSetAlreadyVisited, |
348 | 62.5k | nRecLevel + 1, bDryRun) && |
349 | 3.61k | bDryRun) |
350 | 1.48k | return true; |
351 | 62.5k | } |
352 | 64.3k | } |
353 | 16.7k | } |
354 | 21.3k | } |
355 | 9.46k | else if (poK->GetType() == PDFObjectType_Dictionary) |
356 | 6.07k | { |
357 | 6.07k | if (ExploreTree(poK, aoSetAlreadyVisited, nRecLevel + 1, bDryRun) && |
358 | 0 | bDryRun) |
359 | 0 | return true; |
360 | 6.07k | } |
361 | | |
362 | 26.8k | return bRet; |
363 | 30.7k | } |
364 | | |
365 | | /************************************************************************/ |
366 | | /* GetGeometryFromMCID() */ |
367 | | /************************************************************************/ |
368 | | |
369 | | OGRGeometry *PDFDataset::GetGeometryFromMCID(int nMCID) |
370 | 55.7k | { |
371 | 55.7k | auto oMapIter = m_oMapMCID.find(nMCID); |
372 | 55.7k | if (oMapIter != m_oMapMCID.end()) |
373 | 18.0k | return oMapIter->second.get(); |
374 | 37.7k | else |
375 | 37.7k | return nullptr; |
376 | 55.7k | } |
377 | | |
378 | | /************************************************************************/ |
379 | | /* GraphicState::PreMultiplyBy() */ |
380 | | /************************************************************************/ |
381 | | |
382 | | void PDFDataset::GraphicState::PreMultiplyBy(double adfMatrix[6]) |
383 | 37.0k | { |
384 | | /* |
385 | | [ a b 0 ] [ a' b' 0] [ aa' + bc' ab' + bd' 0 ] |
386 | | [ c d 0 ] * [ c' d' 0] = [ ca' + dc' cb' + dd' 0 ] |
387 | | [ e f 1 ] [ e' f' 1] [ ea' + fc' + e' eb' + fd' + f' 1 ] |
388 | | */ |
389 | | |
390 | | // Be careful about the multiplication order! |
391 | | // PDF reference version 1.7, page 209: |
392 | | // when a sequence of transformations is carried out, the matrix |
393 | | // representing the combined transformation (M′) is calculated |
394 | | // by premultiplying the matrix representing the additional transformation (MT) |
395 | | // with the one representing all previously existing transformations (M) |
396 | | |
397 | 37.0k | double a = adfMatrix[0]; |
398 | 37.0k | double b = adfMatrix[1]; |
399 | 37.0k | double c = adfMatrix[2]; |
400 | 37.0k | double d = adfMatrix[3]; |
401 | 37.0k | double e = adfMatrix[4]; |
402 | 37.0k | double f = adfMatrix[5]; |
403 | 37.0k | double ap = adfCM[0]; |
404 | 37.0k | double bp = adfCM[1]; |
405 | 37.0k | double cp = adfCM[2]; |
406 | 37.0k | double dp = adfCM[3]; |
407 | 37.0k | double ep = adfCM[4]; |
408 | 37.0k | double fp = adfCM[5]; |
409 | 37.0k | adfCM[0] = a * ap + b * cp; |
410 | 37.0k | adfCM[1] = a * bp + b * dp; |
411 | 37.0k | adfCM[2] = c * ap + d * cp; |
412 | 37.0k | adfCM[3] = c * bp + d * dp; |
413 | 37.0k | adfCM[4] = e * ap + f * cp + ep; |
414 | 37.0k | adfCM[5] = e * bp + f * dp + fp; |
415 | 37.0k | } |
416 | | |
417 | | /************************************************************************/ |
418 | | /* GraphicState::ApplyMatrix() */ |
419 | | /************************************************************************/ |
420 | | |
421 | | void PDFDataset::GraphicState::ApplyMatrix(double adfCoords[2]) const |
422 | 1.59M | { |
423 | 1.59M | double x = adfCoords[0]; |
424 | 1.59M | double y = adfCoords[1]; |
425 | | |
426 | 1.59M | adfCoords[0] = x * adfCM[0] + y * adfCM[2] + adfCM[4]; |
427 | 1.59M | adfCoords[1] = x * adfCM[1] + y * adfCM[3] + adfCM[5]; |
428 | 1.59M | } |
429 | | |
430 | | /************************************************************************/ |
431 | | /* PDFCoordsToSRSCoords() */ |
432 | | /************************************************************************/ |
433 | | |
434 | | void PDFDataset::PDFCoordsToSRSCoords(double x, double y, double &X, double &Y) |
435 | 1.85M | { |
436 | 1.85M | x = x / m_dfPageWidth * nRasterXSize; |
437 | 1.85M | if (m_bGeoTransformValid) |
438 | 661k | y = (1 - y / m_dfPageHeight) * nRasterYSize; |
439 | 1.18M | else |
440 | 1.18M | y = (y / m_dfPageHeight) * nRasterYSize; |
441 | | |
442 | 1.85M | X = m_gt.xorig + x * m_gt.xscale + y * m_gt.xrot; |
443 | 1.85M | Y = m_gt.yorig + x * m_gt.yrot + y * m_gt.yscale; |
444 | | |
445 | 1.85M | if (fabs(X - std::round(X)) < 1e-8) |
446 | 431k | X = std::round(X); |
447 | 1.85M | if (fabs(Y - std::round(Y)) < 1e-8) |
448 | 447k | Y = std::round(Y); |
449 | 1.85M | } |
450 | | |
451 | | /************************************************************************/ |
452 | | /* PDFGetCircleCenter() */ |
453 | | /************************************************************************/ |
454 | | |
455 | | /* Return the center of a circle, or NULL if it is not recognized */ |
456 | | |
457 | | static std::unique_ptr<OGRPoint> PDFGetCircleCenter(OGRLineString *poLS) |
458 | 14.6k | { |
459 | 14.6k | if (poLS == nullptr || poLS->getNumPoints() != 1 + 4 * BEZIER_STEPS) |
460 | 0 | return nullptr; |
461 | | |
462 | 14.6k | if (poLS->getY(0 * BEZIER_STEPS) == poLS->getY(2 * BEZIER_STEPS) && |
463 | 14.6k | poLS->getX(1 * BEZIER_STEPS) == poLS->getX(3 * BEZIER_STEPS) && |
464 | 14.6k | fabs((poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2 - |
465 | 14.6k | poLS->getX(1 * BEZIER_STEPS)) < EPSILON && |
466 | 10.6k | fabs((poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2 - |
467 | 10.6k | poLS->getY(0 * BEZIER_STEPS)) < EPSILON) |
468 | 10.3k | { |
469 | 10.3k | return std::make_unique<OGRPoint>( |
470 | 10.3k | (poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2, |
471 | 10.3k | (poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2); |
472 | 10.3k | } |
473 | 4.34k | return nullptr; |
474 | 14.6k | } |
475 | | |
476 | | /************************************************************************/ |
477 | | /* PDFGetSquareCenter() */ |
478 | | /************************************************************************/ |
479 | | |
480 | | /* Return the center of a square, or NULL if it is not recognized */ |
481 | | |
482 | | static std::unique_ptr<OGRPoint> PDFGetSquareCenter(OGRLineString *poLS) |
483 | 8.33k | { |
484 | 8.33k | if (poLS == nullptr || poLS->getNumPoints() < 4 || poLS->getNumPoints() > 5) |
485 | 0 | return nullptr; |
486 | | |
487 | 8.33k | if (poLS->getX(0) == poLS->getX(3) && poLS->getY(0) == poLS->getY(1) && |
488 | 7.90k | poLS->getX(1) == poLS->getX(2) && poLS->getY(2) == poLS->getY(3) && |
489 | 7.89k | fabs(fabs(poLS->getX(0) - poLS->getX(1)) - |
490 | 7.89k | fabs(poLS->getY(0) - poLS->getY(3))) < EPSILON) |
491 | 24 | { |
492 | 24 | return std::make_unique<OGRPoint>((poLS->getX(0) + poLS->getX(1)) / 2, |
493 | 24 | (poLS->getY(0) + poLS->getY(3)) / 2); |
494 | 24 | } |
495 | 8.30k | return nullptr; |
496 | 8.33k | } |
497 | | |
498 | | /************************************************************************/ |
499 | | /* PDFGetTriangleCenter() */ |
500 | | /************************************************************************/ |
501 | | |
502 | | /* Return the center of a equilateral triangle, or NULL if it is not recognized |
503 | | */ |
504 | | |
505 | | static std::unique_ptr<OGRPoint> PDFGetTriangleCenter(OGRLineString *poLS) |
506 | 30.2k | { |
507 | 30.2k | if (poLS == nullptr || poLS->getNumPoints() < 3 || poLS->getNumPoints() > 4) |
508 | 0 | return nullptr; |
509 | | |
510 | 30.2k | double dfSqD1 = SQUARE(poLS->getX(0) - poLS->getX(1)) + |
511 | 30.2k | SQUARE(poLS->getY(0) - poLS->getY(1)); |
512 | 30.2k | double dfSqD2 = SQUARE(poLS->getX(1) - poLS->getX(2)) + |
513 | 30.2k | SQUARE(poLS->getY(1) - poLS->getY(2)); |
514 | 30.2k | double dfSqD3 = SQUARE(poLS->getX(0) - poLS->getX(2)) + |
515 | 30.2k | SQUARE(poLS->getY(0) - poLS->getY(2)); |
516 | 30.2k | if (fabs(dfSqD1 - dfSqD2) < EPSILON && fabs(dfSqD2 - dfSqD3) < EPSILON) |
517 | 675 | { |
518 | 675 | return std::make_unique<OGRPoint>( |
519 | 675 | (poLS->getX(0) + poLS->getX(1) + poLS->getX(2)) / 3, |
520 | 675 | (poLS->getY(0) + poLS->getY(1) + poLS->getY(2)) / 3); |
521 | 675 | } |
522 | 29.5k | return nullptr; |
523 | 30.2k | } |
524 | | |
525 | | /************************************************************************/ |
526 | | /* PDFGetStarCenter() */ |
527 | | /************************************************************************/ |
528 | | |
529 | | /* Return the center of a 5-point star, or NULL if it is not recognized */ |
530 | | |
531 | | static std::unique_ptr<OGRPoint> PDFGetStarCenter(OGRLineString *poLS) |
532 | 4 | { |
533 | 4 | if (poLS == nullptr || poLS->getNumPoints() < 10 || |
534 | 4 | poLS->getNumPoints() > 11) |
535 | 0 | return nullptr; |
536 | | |
537 | 4 | double dfSqD01 = SQUARE(poLS->getX(0) - poLS->getX(1)) + |
538 | 4 | SQUARE(poLS->getY(0) - poLS->getY(1)); |
539 | 4 | double dfSqD02 = SQUARE(poLS->getX(0) - poLS->getX(2)) + |
540 | 4 | SQUARE(poLS->getY(0) - poLS->getY(2)); |
541 | 4 | double dfSqD13 = SQUARE(poLS->getX(1) - poLS->getX(3)) + |
542 | 4 | SQUARE(poLS->getY(1) - poLS->getY(3)); |
543 | 4 | const double dfSin18divSin126 = 0.38196601125; |
544 | 4 | if (dfSqD02 == 0) |
545 | 0 | return nullptr; |
546 | 4 | int bOK = fabs(dfSqD13 / dfSqD02 - SQUARE(dfSin18divSin126)) < EPSILON; |
547 | 4 | for (int i = 1; i < 10 && bOK; i++) |
548 | 0 | { |
549 | 0 | double dfSqDiip1 = SQUARE(poLS->getX(i) - poLS->getX((i + 1) % 10)) + |
550 | 0 | SQUARE(poLS->getY(i) - poLS->getY((i + 1) % 10)); |
551 | 0 | if (fabs(dfSqDiip1 - dfSqD01) > EPSILON) |
552 | 0 | { |
553 | 0 | bOK = FALSE; |
554 | 0 | } |
555 | 0 | double dfSqDiip2 = SQUARE(poLS->getX(i) - poLS->getX((i + 2) % 10)) + |
556 | 0 | SQUARE(poLS->getY(i) - poLS->getY((i + 2) % 10)); |
557 | 0 | if ((i % 2) == 1 && fabs(dfSqDiip2 - dfSqD13) > EPSILON) |
558 | 0 | { |
559 | 0 | bOK = FALSE; |
560 | 0 | } |
561 | 0 | if ((i % 2) == 0 && fabs(dfSqDiip2 - dfSqD02) > EPSILON) |
562 | 0 | { |
563 | 0 | bOK = FALSE; |
564 | 0 | } |
565 | 0 | } |
566 | 4 | if (bOK) |
567 | 0 | { |
568 | 0 | return std::make_unique<OGRPoint>( |
569 | 0 | (poLS->getX(0) + poLS->getX(2) + poLS->getX(4) + poLS->getX(6) + |
570 | 0 | poLS->getX(8)) / |
571 | 0 | 5, |
572 | 0 | (poLS->getY(0) + poLS->getY(2) + poLS->getY(4) + poLS->getY(6) + |
573 | 0 | poLS->getY(8)) / |
574 | 0 | 5); |
575 | 0 | } |
576 | 4 | return nullptr; |
577 | 4 | } |
578 | | |
579 | | /************************************************************************/ |
580 | | /* UnstackTokens() */ |
581 | | /************************************************************************/ |
582 | | |
583 | | int PDFDataset::UnstackTokens( |
584 | | const char *pszToken, int nRequiredArgs, |
585 | | char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE], int &nTokenStackSize, |
586 | | double *adfCoords) |
587 | 1.54M | { |
588 | 1.54M | if (nTokenStackSize < nRequiredArgs) |
589 | 199 | { |
590 | 199 | CPLDebug("PDF", "not enough arguments for %s", pszToken); |
591 | 199 | return FALSE; |
592 | 199 | } |
593 | 1.54M | nTokenStackSize -= nRequiredArgs; |
594 | 5.23M | for (int i = 0; i < nRequiredArgs; i++) |
595 | 3.69M | { |
596 | 3.69M | adfCoords[i] = CPLAtof(aszTokenStack[nTokenStackSize + i]); |
597 | 3.69M | } |
598 | 1.54M | return TRUE; |
599 | 1.54M | } |
600 | | |
601 | | /************************************************************************/ |
602 | | /* AddBezierCurve() */ |
603 | | /************************************************************************/ |
604 | | |
605 | | static void AddBezierCurve(std::vector<double> &oCoords, const double *x0_y0, |
606 | | const double *x1_y1, const double *x2_y2, |
607 | | const double *x3_y3) |
608 | 62.1k | { |
609 | 62.1k | double x0 = x0_y0[0]; |
610 | 62.1k | double y0 = x0_y0[1]; |
611 | 62.1k | double x1 = x1_y1[0]; |
612 | 62.1k | double y1 = x1_y1[1]; |
613 | 62.1k | double x2 = x2_y2[0]; |
614 | 62.1k | double y2 = x2_y2[1]; |
615 | 62.1k | double x3 = x3_y3[0]; |
616 | 62.1k | double y3 = x3_y3[1]; |
617 | 621k | for (int i = 1; i < BEZIER_STEPS; i++) |
618 | 559k | { |
619 | 559k | const double t = static_cast<double>(i) / BEZIER_STEPS; |
620 | 559k | const double t2 = t * t; |
621 | 559k | const double t3 = t2 * t; |
622 | 559k | const double oneMinust = 1 - t; |
623 | 559k | const double oneMinust2 = oneMinust * oneMinust; |
624 | 559k | const double oneMinust3 = oneMinust2 * oneMinust; |
625 | 559k | const double three_t_oneMinust = 3 * t * oneMinust; |
626 | 559k | const double x = oneMinust3 * x0 + |
627 | 559k | three_t_oneMinust * (oneMinust * x1 + t * x2) + |
628 | 559k | t3 * x3; |
629 | 559k | const double y = oneMinust3 * y0 + |
630 | 559k | three_t_oneMinust * (oneMinust * y1 + t * y2) + |
631 | 559k | t3 * y3; |
632 | 559k | oCoords.push_back(x); |
633 | 559k | oCoords.push_back(y); |
634 | 559k | } |
635 | 62.1k | oCoords.push_back(x3); |
636 | 62.1k | oCoords.push_back(y3); |
637 | 62.1k | } |
638 | | |
639 | | /************************************************************************/ |
640 | | /* ParseContent() */ |
641 | | /************************************************************************/ |
642 | | |
643 | 5.21M | #define NEW_SUBPATH -99 |
644 | 4.10M | #define CLOSE_SUBPATH -98 |
645 | 3.87M | #define FILL_SUBPATH -97 |
646 | | |
647 | | OGRGeometry *PDFDataset::ParseContent( |
648 | | const char *pszContent, GDALPDFObject *poResources, bool bCollectAllObjects, |
649 | | bool bInitBDCStack, bool bMatchQ, |
650 | | const std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer, |
651 | | const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer, |
652 | | const GraphicState &graphicStateIn, OGRPDFLayer *poCurLayer, int nRecLevel) |
653 | 75.2k | { |
654 | 75.2k | if (nRecLevel == 32) |
655 | 0 | { |
656 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
657 | 0 | "Too many recursion levels in ParseContent()"); |
658 | 0 | return nullptr; |
659 | 0 | } |
660 | 75.2k | if (CPLTestBool(CPLGetConfigOption("PDF_DUMP_CONTENT", "NO"))) |
661 | 0 | { |
662 | 0 | static int counter = 1; |
663 | 0 | FILE *f = fopen(CPLSPrintf("content%d.txt", counter), "wb"); |
664 | 0 | ++counter; |
665 | 0 | fwrite(pszContent, 1, strlen(pszContent), f); |
666 | 0 | fclose(f); |
667 | 0 | } |
668 | 75.2k | const char *pszContentIni = pszContent; |
669 | | #ifdef DEBUG_VERBOSE |
670 | | CPLDebug("PDF", "Initial layer: %s", |
671 | | poCurLayer ? poCurLayer->GetName() : "(null)"); |
672 | | #endif |
673 | | |
674 | 75.2k | #define PUSH(aszTokenStack, str, strlen) \ |
675 | 4.15M | do \ |
676 | 4.15M | { \ |
677 | 4.15M | if (nTokenStackSize < TOKEN_STACK_SIZE) \ |
678 | 4.15M | memcpy(aszTokenStack[nTokenStackSize++], str, strlen + 1); \ |
679 | 4.15M | else \ |
680 | 4.15M | { \ |
681 | 2.38k | CPLError(CE_Failure, CPLE_AppDefined, \ |
682 | 2.38k | "Max token stack size reached"); \ |
683 | 2.38k | return nullptr; \ |
684 | 4.15M | }; \ |
685 | 4.15M | } while (false) |
686 | | |
687 | 75.2k | #define ADD_CHAR(szToken, c) \ |
688 | 41.2M | do \ |
689 | 41.2M | { \ |
690 | 41.2M | if (nTokenSize < MAX_TOKEN_SIZE - 1) \ |
691 | 41.2M | { \ |
692 | 41.2M | szToken[nTokenSize++] = c; \ |
693 | 41.2M | szToken[nTokenSize] = '\0'; \ |
694 | 41.2M | } \ |
695 | 41.2M | else \ |
696 | 41.2M | { \ |
697 | 656 | CPLError(CE_Failure, CPLE_AppDefined, "Max token size reached"); \ |
698 | 656 | return nullptr; \ |
699 | 41.2M | }; \ |
700 | 41.2M | } while (false) |
701 | | |
702 | 75.2k | char szToken[MAX_TOKEN_SIZE]; |
703 | 75.2k | int nTokenSize = 0; |
704 | 75.2k | char ch; |
705 | 75.2k | char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE]; |
706 | 75.2k | int nTokenStackSize = 0; |
707 | 75.2k | int bInString = FALSE; |
708 | 75.2k | int nBDCOrBMCLevel = 0; |
709 | 75.2k | int nParenthesisLevel = 0; |
710 | 75.2k | int nArrayLevel = 0; |
711 | 75.2k | int nBTLevel = 0; |
712 | | |
713 | 75.2k | GraphicState oGS(graphicStateIn); |
714 | 75.2k | std::stack<GraphicState> oGSStack; |
715 | 75.2k | std::stack<OGRPDFLayer *> oLayerStack; |
716 | | |
717 | 75.2k | std::vector<double> oCoords; |
718 | 75.2k | int bHasFoundFill = FALSE; |
719 | 75.2k | int bHasMultiPart = FALSE; |
720 | | |
721 | 75.2k | szToken[0] = '\0'; |
722 | | |
723 | 75.2k | if (bInitBDCStack) |
724 | 29.0k | { |
725 | 29.0k | PUSH(aszTokenStack, "dummy", 5); |
726 | 29.0k | PUSH(aszTokenStack, "dummy", 5); |
727 | 29.0k | oLayerStack.push(nullptr); |
728 | 29.0k | } |
729 | | |
730 | 75.2k | int nLineNumber = 0; |
731 | | |
732 | 52.7M | while ((ch = *pszContent) != '\0') |
733 | 52.6M | { |
734 | 52.6M | int bPushToken = FALSE; |
735 | | |
736 | 52.6M | if (!bInString && ch == '%') |
737 | 467 | { |
738 | | /* Skip comments until end-of-line */ |
739 | 17.8k | while ((ch = *pszContent) != '\0') |
740 | 17.8k | { |
741 | 17.8k | if (ch == '\r' || ch == '\n') |
742 | 464 | break; |
743 | 17.3k | pszContent++; |
744 | 17.3k | } |
745 | 467 | if (ch == 0) |
746 | 3 | break; |
747 | 464 | ++nLineNumber; |
748 | 464 | if (ch == '\r' && pszContent[1] == '\n') |
749 | 15 | { |
750 | 15 | ++pszContent; |
751 | 15 | } |
752 | 464 | } |
753 | 52.6M | else if (!bInString && (ch == ' ' || ch == '\r' || ch == '\n')) |
754 | 7.72M | { |
755 | 7.72M | if (ch == '\r') |
756 | 420k | { |
757 | 420k | ++nLineNumber; |
758 | 420k | if (pszContent[1] == '\n') |
759 | 373k | { |
760 | 373k | ++pszContent; |
761 | 373k | } |
762 | 420k | } |
763 | 7.29M | else if (ch == '\n') |
764 | 1.93M | ++nLineNumber; |
765 | 7.72M | bPushToken = TRUE; |
766 | 7.72M | } |
767 | | |
768 | | /* Ignore arrays */ |
769 | 44.9M | else if (!bInString && nTokenSize == 0 && ch == '[') |
770 | 34.3k | { |
771 | 34.3k | nArrayLevel++; |
772 | 34.3k | } |
773 | 44.9M | else if (!bInString && nArrayLevel && ch == ']') |
774 | 33.4k | { |
775 | 33.4k | nArrayLevel--; |
776 | 33.4k | nTokenSize = 0; // completely ignore content in arrays |
777 | 33.4k | } |
778 | | |
779 | 44.8M | else if (!bInString && nTokenSize == 0 && ch == '(') |
780 | 140k | { |
781 | 140k | bInString = TRUE; |
782 | 140k | nParenthesisLevel++; |
783 | 140k | ADD_CHAR(szToken, ch); |
784 | 140k | } |
785 | 44.7M | else if (bInString && ch == '(') |
786 | 76.6k | { |
787 | 76.6k | nParenthesisLevel++; |
788 | 76.6k | ADD_CHAR(szToken, ch); |
789 | 76.6k | } |
790 | 44.6M | else if (bInString && ch == ')') |
791 | 210k | { |
792 | 210k | nParenthesisLevel--; |
793 | 210k | ADD_CHAR(szToken, ch); |
794 | 210k | if (nParenthesisLevel == 0) |
795 | 140k | { |
796 | 140k | bInString = FALSE; |
797 | 140k | bPushToken = TRUE; |
798 | 140k | } |
799 | 210k | } |
800 | 44.4M | else if (bInString && ch == '\\') |
801 | 42.2k | { |
802 | 42.2k | const auto nextCh = pszContent[1]; |
803 | 42.2k | if (nextCh == 'n') |
804 | 28 | { |
805 | 28 | ADD_CHAR(szToken, '\n'); |
806 | 28 | pszContent++; |
807 | 28 | } |
808 | 42.1k | else if (nextCh == 'r') |
809 | 30 | { |
810 | 30 | ADD_CHAR(szToken, '\r'); |
811 | 30 | pszContent++; |
812 | 30 | } |
813 | 42.1k | else if (nextCh == 't') |
814 | 0 | { |
815 | 0 | ADD_CHAR(szToken, '\t'); |
816 | 0 | pszContent++; |
817 | 0 | } |
818 | 42.1k | else if (nextCh == 'b') |
819 | 0 | { |
820 | 0 | ADD_CHAR(szToken, '\b'); |
821 | 0 | pszContent++; |
822 | 0 | } |
823 | 42.1k | else if (nextCh == '(' || nextCh == ')' || nextCh == '\\') |
824 | 672 | { |
825 | 672 | ADD_CHAR(szToken, nextCh); |
826 | 671 | pszContent++; |
827 | 671 | } |
828 | 41.4k | else if (nextCh >= '0' && nextCh <= '7' && pszContent[2] >= '0' && |
829 | 35.7k | pszContent[2] <= '7' && pszContent[3] >= '0' && |
830 | 26.9k | pszContent[3] <= '7') |
831 | 25.1k | { |
832 | 25.1k | ADD_CHAR(szToken, |
833 | 25.1k | ((nextCh - '\0') * 64 + (pszContent[2] - '\0') * 8 + |
834 | 25.1k | pszContent[3] - '\0')); |
835 | 25.0k | pszContent += 3; |
836 | 25.0k | } |
837 | 16.3k | else if (nextCh == '\n') |
838 | 735 | { |
839 | 735 | if (pszContent[2] == '\r') |
840 | 3 | pszContent += 2; |
841 | 732 | else |
842 | 732 | pszContent++; |
843 | 735 | } |
844 | 15.6k | else if (nextCh == '\r') |
845 | 104 | { |
846 | 104 | pszContent++; |
847 | 104 | } |
848 | 42.2k | } |
849 | 44.4M | else if (ch == '<' && pszContent[1] == '<' && nTokenSize == 0) |
850 | 4.44k | { |
851 | 4.44k | int nDictDepth = 0; |
852 | | |
853 | 73.4k | while (*pszContent != '\0') |
854 | 73.3k | { |
855 | 73.3k | if (pszContent[0] == '<' && pszContent[1] == '<') |
856 | 4.64k | { |
857 | 4.64k | ADD_CHAR(szToken, '<'); |
858 | 4.64k | ADD_CHAR(szToken, '<'); |
859 | 4.64k | nDictDepth++; |
860 | 4.64k | pszContent += 2; |
861 | 4.64k | } |
862 | 68.7k | else if (pszContent[0] == '>' && pszContent[1] == '>') |
863 | 4.50k | { |
864 | 4.50k | ADD_CHAR(szToken, '>'); |
865 | 4.50k | ADD_CHAR(szToken, '>'); |
866 | 4.50k | nDictDepth--; |
867 | 4.50k | pszContent += 2; |
868 | 4.50k | if (nDictDepth == 0) |
869 | 4.33k | break; |
870 | 4.50k | } |
871 | 64.2k | else |
872 | 64.2k | { |
873 | 64.2k | ADD_CHAR(szToken, *pszContent); |
874 | 64.1k | pszContent++; |
875 | 64.1k | } |
876 | 73.3k | } |
877 | 4.38k | if (nDictDepth == 0) |
878 | 4.33k | { |
879 | 4.33k | bPushToken = TRUE; |
880 | 4.33k | pszContent--; |
881 | 4.33k | } |
882 | 52 | else |
883 | 52 | break; |
884 | 4.38k | } |
885 | 44.4M | else |
886 | 44.4M | { |
887 | | // Do not create too long tokens in arrays, that we will ignore |
888 | | // anyway |
889 | 44.4M | if (nArrayLevel == 0 || nTokenSize == 0) |
890 | 40.6M | { |
891 | 40.6M | ADD_CHAR(szToken, ch); |
892 | 40.6M | } |
893 | 44.4M | } |
894 | | |
895 | 52.6M | pszContent++; |
896 | 52.6M | if (pszContent[0] == '\0') |
897 | 42.1k | bPushToken = TRUE; |
898 | | |
899 | 52.6M | #define EQUAL1(szToken, s) (szToken[0] == s[0] && szToken[1] == '\0') |
900 | 52.6M | #define EQUAL2(szToken, s) \ |
901 | 52.6M | (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == '\0') |
902 | 52.6M | #define EQUAL3(szToken, s) \ |
903 | 52.6M | (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == s[2] && \ |
904 | 22.9M | szToken[3] == '\0') |
905 | | |
906 | 52.6M | if (bPushToken && nTokenSize) |
907 | 7.66M | { |
908 | 7.66M | if (EQUAL2(szToken, "BI")) |
909 | 15 | { |
910 | 266k | while (*pszContent != '\0') |
911 | 266k | { |
912 | 266k | if (pszContent[0] == 'E' && pszContent[1] == 'I' && |
913 | 1 | pszContent[2] == ' ') |
914 | 1 | { |
915 | 1 | break; |
916 | 1 | } |
917 | 266k | pszContent++; |
918 | 266k | } |
919 | 15 | if (pszContent[0] == 'E') |
920 | 1 | pszContent += 3; |
921 | 14 | else |
922 | 14 | { |
923 | 14 | CPLDebug("PDF", |
924 | 14 | "ParseContent(), line %d: return at line %d of " |
925 | 14 | "content stream", |
926 | 14 | __LINE__, nLineNumber); |
927 | 14 | return nullptr; |
928 | 14 | } |
929 | 15 | } |
930 | 7.66M | else if (EQUAL3(szToken, "BDC")) |
931 | 64.7k | { |
932 | 64.7k | if (nTokenStackSize < 2) |
933 | 32 | { |
934 | 32 | CPLDebug("PDF", "not enough arguments for %s", szToken); |
935 | 32 | CPLDebug("PDF", |
936 | 32 | "ParseContent(), line %d: return at line %d of " |
937 | 32 | "content stream", |
938 | 32 | __LINE__, nLineNumber); |
939 | 32 | return nullptr; |
940 | 32 | } |
941 | 64.7k | nTokenStackSize -= 2; |
942 | 64.7k | const char *pszOC = aszTokenStack[nTokenStackSize]; |
943 | 64.7k | const char *pszOCGName = aszTokenStack[nTokenStackSize + 1]; |
944 | | |
945 | 64.7k | nBDCOrBMCLevel++; |
946 | | |
947 | 64.7k | if (EQUAL3(pszOC, "/OC") && pszOCGName[0] == '/') |
948 | 26.3k | { |
949 | 26.3k | const auto oIter = oMapPropertyToLayer.find(pszOCGName + 1); |
950 | 26.3k | if (oIter != oMapPropertyToLayer.end()) |
951 | 8.32k | { |
952 | 8.32k | poCurLayer = oIter->second; |
953 | 8.32k | } |
954 | 26.3k | } |
955 | | #ifdef DEBUG_VERBOSE |
956 | | CPLDebug("PDF", "%s %s BDC -> Cur layer : %s", pszOC, |
957 | | pszOCGName, |
958 | | poCurLayer ? poCurLayer->GetName() : "(null)"); |
959 | | #endif |
960 | 64.7k | oLayerStack.push(poCurLayer); |
961 | 64.7k | } |
962 | 7.60M | else if (EQUAL3(szToken, "BMC")) |
963 | 64 | { |
964 | 64 | if (nTokenStackSize < 1) |
965 | 0 | { |
966 | 0 | CPLDebug("PDF", "not enough arguments for %s", szToken); |
967 | 0 | CPLDebug("PDF", |
968 | 0 | "ParseContent(), line %d: return at line %d of " |
969 | 0 | "content stream", |
970 | 0 | __LINE__, nLineNumber); |
971 | 0 | return nullptr; |
972 | 0 | } |
973 | 64 | nTokenStackSize -= 1; |
974 | | |
975 | 64 | nBDCOrBMCLevel++; |
976 | 64 | oLayerStack.push(poCurLayer); |
977 | 64 | } |
978 | 7.60M | else if (EQUAL3(szToken, "EMC")) |
979 | 35.0k | { |
980 | | // CPLDebug("PDF", "EMC"); |
981 | 35.0k | if (!oLayerStack.empty()) |
982 | 34.7k | { |
983 | 34.7k | oLayerStack.pop(); |
984 | 34.7k | if (!oLayerStack.empty()) |
985 | 10.6k | poCurLayer = oLayerStack.top(); |
986 | 24.1k | else |
987 | 24.1k | poCurLayer = nullptr; |
988 | | |
989 | | #ifdef DEBUG_VERBOSE |
990 | | CPLDebug("PDF", "EMC -> Cur layer : %s", |
991 | | poCurLayer ? poCurLayer->GetName() : "(null)"); |
992 | | #endif |
993 | 34.7k | } |
994 | 245 | else |
995 | 245 | { |
996 | 245 | CPLDebug( |
997 | 245 | "PDF", |
998 | 245 | "Should not happen at line %d: offset %d in stream", |
999 | 245 | __LINE__, int(pszContent - pszContentIni)); |
1000 | 245 | poCurLayer = nullptr; |
1001 | | // return NULL; |
1002 | 245 | } |
1003 | | |
1004 | 35.0k | nBDCOrBMCLevel--; |
1005 | 35.0k | if (nBDCOrBMCLevel == 0 && bInitBDCStack) |
1006 | 9.30k | break; |
1007 | 35.0k | } |
1008 | | |
1009 | | /* Ignore any text stuff */ |
1010 | 7.56M | else if (EQUAL2(szToken, "BT")) |
1011 | 14.0k | nBTLevel++; |
1012 | 7.55M | else if (EQUAL2(szToken, "ET")) |
1013 | 11.2k | { |
1014 | 11.2k | nBTLevel--; |
1015 | 11.2k | if (nBTLevel < 0) |
1016 | 13 | { |
1017 | 13 | CPLDebug( |
1018 | 13 | "PDF", |
1019 | 13 | "Should not happen at line %d: offset %d in stream", |
1020 | 13 | __LINE__, int(pszContent - pszContentIni)); |
1021 | 13 | CPLDebug("PDF", |
1022 | 13 | "ParseContent(), line %d: return at line %d of " |
1023 | 13 | "content stream", |
1024 | 13 | __LINE__, nLineNumber); |
1025 | 13 | return nullptr; |
1026 | 13 | } |
1027 | 11.2k | } |
1028 | 7.54M | else if (!nArrayLevel && !nBTLevel) |
1029 | 6.31M | { |
1030 | 6.31M | int bEmitFeature = FALSE; |
1031 | | |
1032 | 6.31M | if (szToken[0] < 'A') |
1033 | 4.08M | { |
1034 | 4.08M | PUSH(aszTokenStack, szToken, nTokenSize); |
1035 | 4.08M | } |
1036 | 2.23M | else if (EQUAL1(szToken, "q")) |
1037 | 94.9k | { |
1038 | 94.9k | oGSStack.push(oGS); |
1039 | 94.9k | } |
1040 | 2.13M | else if (EQUAL1(szToken, "Q")) |
1041 | 79.5k | { |
1042 | 79.5k | if (oGSStack.empty()) |
1043 | 314 | { |
1044 | 314 | CPLDebug("PDF", "not enough arguments for %s", szToken); |
1045 | 314 | CPLDebug("PDF", |
1046 | 314 | "ParseContent(), line %d: return at line %d " |
1047 | 314 | "of content stream", |
1048 | 314 | __LINE__, nLineNumber); |
1049 | 314 | return nullptr; |
1050 | 314 | } |
1051 | | |
1052 | 79.1k | oGS = oGSStack.top(); |
1053 | 79.1k | oGSStack.pop(); |
1054 | | |
1055 | 79.1k | if (oGSStack.empty() && bMatchQ) |
1056 | 0 | break; |
1057 | 79.1k | } |
1058 | 2.05M | else if (EQUAL2(szToken, "cm")) |
1059 | 37.1k | { |
1060 | 37.1k | double adfMatrix[6]; |
1061 | 37.1k | if (!UnstackTokens(szToken, 6, aszTokenStack, |
1062 | 37.1k | nTokenStackSize, adfMatrix)) |
1063 | 10 | { |
1064 | 10 | CPLDebug( |
1065 | 10 | "PDF", |
1066 | 10 | "Should not happen at line %d: offset %d in stream", |
1067 | 10 | __LINE__, int(pszContent - pszContentIni)); |
1068 | 10 | CPLDebug("PDF", |
1069 | 10 | "ParseContent(), line %d: return at line %d " |
1070 | 10 | "of content stream", |
1071 | 10 | __LINE__, nLineNumber); |
1072 | 10 | return nullptr; |
1073 | 10 | } |
1074 | | |
1075 | 37.0k | oGS.PreMultiplyBy(adfMatrix); |
1076 | 37.0k | } |
1077 | 2.02M | else if (EQUAL1(szToken, "b") || /* closepath, fill, stroke */ |
1078 | 2.02M | EQUAL2(szToken, "b*") /* closepath, eofill, stroke */) |
1079 | 19.4k | { |
1080 | 19.4k | if (!(!oCoords.empty() && |
1081 | 19.3k | oCoords[oCoords.size() - 2] == CLOSE_SUBPATH && |
1082 | 4.03k | oCoords.back() == CLOSE_SUBPATH)) |
1083 | 15.3k | { |
1084 | 15.3k | oCoords.push_back(CLOSE_SUBPATH); |
1085 | 15.3k | oCoords.push_back(CLOSE_SUBPATH); |
1086 | 15.3k | } |
1087 | 19.4k | oCoords.push_back(FILL_SUBPATH); |
1088 | 19.4k | oCoords.push_back(FILL_SUBPATH); |
1089 | 19.4k | bHasFoundFill = TRUE; |
1090 | | |
1091 | 19.4k | bEmitFeature = TRUE; |
1092 | 19.4k | } |
1093 | 2.00M | else if (EQUAL1(szToken, "B") || /* fill, stroke */ |
1094 | 2.00M | EQUAL2(szToken, "B*") || /* eofill, stroke */ |
1095 | 2.00M | EQUAL1(szToken, "f") || /* fill */ |
1096 | 1.97M | EQUAL1(szToken, "F") || /* fill */ |
1097 | 1.97M | EQUAL2(szToken, "f*") /* eofill */) |
1098 | 24.3k | { |
1099 | 24.3k | oCoords.push_back(FILL_SUBPATH); |
1100 | 24.3k | oCoords.push_back(FILL_SUBPATH); |
1101 | 24.3k | bHasFoundFill = TRUE; |
1102 | | |
1103 | 24.3k | bEmitFeature = TRUE; |
1104 | 24.3k | } |
1105 | 1.97M | else if (EQUAL1(szToken, "h")) /* close subpath */ |
1106 | 12.2k | { |
1107 | 12.2k | if (!(!oCoords.empty() && |
1108 | 12.0k | oCoords[oCoords.size() - 2] == CLOSE_SUBPATH && |
1109 | 1.03k | oCoords.back() == CLOSE_SUBPATH)) |
1110 | 11.2k | { |
1111 | 11.2k | oCoords.push_back(CLOSE_SUBPATH); |
1112 | 11.2k | oCoords.push_back(CLOSE_SUBPATH); |
1113 | 11.2k | } |
1114 | 12.2k | } |
1115 | 1.96M | else if (EQUAL1( |
1116 | 1.96M | szToken, |
1117 | 1.96M | "n")) /* new subpath without stroking or filling */ |
1118 | 40.8k | { |
1119 | 40.8k | oCoords.resize(0); |
1120 | 40.8k | } |
1121 | 1.92M | else if (EQUAL1(szToken, "s")) /* close and stroke */ |
1122 | 16 | { |
1123 | 16 | if (!(!oCoords.empty() && |
1124 | 14 | oCoords[oCoords.size() - 2] == CLOSE_SUBPATH && |
1125 | 2 | oCoords.back() == CLOSE_SUBPATH)) |
1126 | 14 | { |
1127 | 14 | oCoords.push_back(CLOSE_SUBPATH); |
1128 | 14 | oCoords.push_back(CLOSE_SUBPATH); |
1129 | 14 | } |
1130 | | |
1131 | 16 | bEmitFeature = TRUE; |
1132 | 16 | } |
1133 | 1.92M | else if (EQUAL1(szToken, "S")) /* stroke */ |
1134 | 51.3k | { |
1135 | 51.3k | bEmitFeature = TRUE; |
1136 | 51.3k | } |
1137 | 1.87M | else if (EQUAL1(szToken, "m") || EQUAL1(szToken, "l")) |
1138 | 1.28M | { |
1139 | 1.28M | double adfCoords[2]; |
1140 | 1.28M | if (!UnstackTokens(szToken, 2, aszTokenStack, |
1141 | 1.28M | nTokenStackSize, adfCoords)) |
1142 | 89 | { |
1143 | 89 | CPLDebug( |
1144 | 89 | "PDF", |
1145 | 89 | "Should not happen at line %d: offset %d in stream", |
1146 | 89 | __LINE__, int(pszContent - pszContentIni)); |
1147 | 89 | CPLDebug("PDF", |
1148 | 89 | "ParseContent(), line %d: return at line %d " |
1149 | 89 | "of content stream", |
1150 | 89 | __LINE__, nLineNumber); |
1151 | 89 | return nullptr; |
1152 | 89 | } |
1153 | | |
1154 | 1.28M | if (EQUAL1(szToken, "m")) |
1155 | 265k | { |
1156 | 265k | if (!oCoords.empty()) |
1157 | 200k | bHasMultiPart = TRUE; |
1158 | 265k | oCoords.push_back(NEW_SUBPATH); |
1159 | 265k | oCoords.push_back(NEW_SUBPATH); |
1160 | 265k | } |
1161 | | |
1162 | 1.28M | oGS.ApplyMatrix(adfCoords); |
1163 | 1.28M | oCoords.push_back(adfCoords[0]); |
1164 | 1.28M | oCoords.push_back(adfCoords[1]); |
1165 | 1.28M | } |
1166 | 582k | else if (EQUAL1(szToken, "c")) /* Bezier curve */ |
1167 | 62.0k | { |
1168 | 62.0k | double adfCoords[6]; |
1169 | 62.0k | if (!UnstackTokens(szToken, 6, aszTokenStack, |
1170 | 62.0k | nTokenStackSize, adfCoords)) |
1171 | 4 | { |
1172 | 4 | CPLDebug( |
1173 | 4 | "PDF", |
1174 | 4 | "Should not happen at line %d: offset %d in stream", |
1175 | 4 | __LINE__, int(pszContent - pszContentIni)); |
1176 | 4 | CPLDebug("PDF", |
1177 | 4 | "ParseContent(), line %d: return at line %d " |
1178 | 4 | "of content stream", |
1179 | 4 | __LINE__, nLineNumber); |
1180 | 4 | return nullptr; |
1181 | 4 | } |
1182 | | |
1183 | 62.0k | oGS.ApplyMatrix(adfCoords + 0); |
1184 | 62.0k | oGS.ApplyMatrix(adfCoords + 2); |
1185 | 62.0k | oGS.ApplyMatrix(adfCoords + 4); |
1186 | 62.0k | AddBezierCurve(oCoords, |
1187 | 62.0k | oCoords.empty() |
1188 | 62.0k | ? &adfCoords[0] |
1189 | 62.0k | : &oCoords[oCoords.size() - 2], |
1190 | 62.0k | &adfCoords[0], &adfCoords[2], &adfCoords[4]); |
1191 | 62.0k | } |
1192 | 520k | else if (EQUAL1(szToken, "v")) /* Bezier curve */ |
1193 | 12 | { |
1194 | 12 | double adfCoords[4]; |
1195 | 12 | if (!UnstackTokens(szToken, 4, aszTokenStack, |
1196 | 12 | nTokenStackSize, adfCoords)) |
1197 | 2 | { |
1198 | 2 | CPLDebug( |
1199 | 2 | "PDF", |
1200 | 2 | "Should not happen at line %d: offset %d in stream", |
1201 | 2 | __LINE__, int(pszContent - pszContentIni)); |
1202 | 2 | CPLDebug("PDF", |
1203 | 2 | "ParseContent(), line %d: return at line %d " |
1204 | 2 | "of content stream", |
1205 | 2 | __LINE__, nLineNumber); |
1206 | 2 | return nullptr; |
1207 | 2 | } |
1208 | | |
1209 | 10 | oGS.ApplyMatrix(adfCoords + 0); |
1210 | 10 | oGS.ApplyMatrix(adfCoords + 2); |
1211 | 10 | AddBezierCurve( |
1212 | 10 | oCoords, |
1213 | 10 | oCoords.empty() ? &adfCoords[0] |
1214 | 10 | : &oCoords[oCoords.size() - 2], |
1215 | 10 | oCoords.empty() ? &adfCoords[0] |
1216 | 10 | : &oCoords[oCoords.size() - 2], |
1217 | 10 | &adfCoords[0], &adfCoords[2]); |
1218 | 10 | } |
1219 | 520k | else if (EQUAL1(szToken, "y")) /* Bezier curve */ |
1220 | 39 | { |
1221 | 39 | double adfCoords[4]; |
1222 | 39 | if (!UnstackTokens(szToken, 4, aszTokenStack, |
1223 | 39 | nTokenStackSize, adfCoords)) |
1224 | 16 | { |
1225 | 16 | CPLDebug( |
1226 | 16 | "PDF", |
1227 | 16 | "Should not happen at line %d: offset %d in stream", |
1228 | 16 | __LINE__, int(pszContent - pszContentIni)); |
1229 | 16 | CPLDebug("PDF", |
1230 | 16 | "ParseContent(), line %d: return at line %d " |
1231 | 16 | "of content stream", |
1232 | 16 | __LINE__, nLineNumber); |
1233 | 16 | return nullptr; |
1234 | 16 | } |
1235 | | |
1236 | 23 | oGS.ApplyMatrix(adfCoords + 0); |
1237 | 23 | oGS.ApplyMatrix(adfCoords + 2); |
1238 | 23 | AddBezierCurve(oCoords, |
1239 | 23 | oCoords.empty() |
1240 | 23 | ? &adfCoords[0] |
1241 | 23 | : &oCoords[oCoords.size() - 2], |
1242 | 23 | &adfCoords[0], &adfCoords[2], &adfCoords[2]); |
1243 | 23 | } |
1244 | 520k | else if (EQUAL2(szToken, "re")) /* Rectangle */ |
1245 | 59.1k | { |
1246 | 59.1k | double adfCoords[4]; |
1247 | 59.1k | if (!UnstackTokens(szToken, 4, aszTokenStack, |
1248 | 59.1k | nTokenStackSize, adfCoords)) |
1249 | 6 | { |
1250 | 6 | CPLDebug( |
1251 | 6 | "PDF", |
1252 | 6 | "Should not happen at line %d: offset %d in stream", |
1253 | 6 | __LINE__, int(pszContent - pszContentIni)); |
1254 | 6 | CPLDebug("PDF", |
1255 | 6 | "ParseContent(), line %d: return at line %d " |
1256 | 6 | "of content stream", |
1257 | 6 | __LINE__, nLineNumber); |
1258 | 6 | return nullptr; |
1259 | 6 | } |
1260 | | |
1261 | 59.1k | adfCoords[2] += adfCoords[0]; |
1262 | 59.1k | adfCoords[3] += adfCoords[1]; |
1263 | | |
1264 | 59.1k | oGS.ApplyMatrix(adfCoords); |
1265 | 59.1k | oGS.ApplyMatrix(adfCoords + 2); |
1266 | | |
1267 | 59.1k | if (!oCoords.empty()) |
1268 | 16.7k | bHasMultiPart = TRUE; |
1269 | 59.1k | oCoords.push_back(NEW_SUBPATH); |
1270 | 59.1k | oCoords.push_back(NEW_SUBPATH); |
1271 | 59.1k | oCoords.push_back(adfCoords[0]); |
1272 | 59.1k | oCoords.push_back(adfCoords[1]); |
1273 | 59.1k | oCoords.push_back(adfCoords[2]); |
1274 | 59.1k | oCoords.push_back(adfCoords[1]); |
1275 | 59.1k | oCoords.push_back(adfCoords[2]); |
1276 | 59.1k | oCoords.push_back(adfCoords[3]); |
1277 | 59.1k | oCoords.push_back(adfCoords[0]); |
1278 | 59.1k | oCoords.push_back(adfCoords[3]); |
1279 | 59.1k | oCoords.push_back(CLOSE_SUBPATH); |
1280 | 59.1k | oCoords.push_back(CLOSE_SUBPATH); |
1281 | 59.1k | } |
1282 | | |
1283 | 461k | else if (EQUAL2(szToken, "Do")) |
1284 | 62.8k | { |
1285 | 62.8k | if (nTokenStackSize == 0) |
1286 | 0 | { |
1287 | 0 | CPLDebug("PDF", "not enough arguments for %s", szToken); |
1288 | 0 | CPLDebug("PDF", |
1289 | 0 | "ParseContent(), line %d: return at line %d " |
1290 | 0 | "of content stream", |
1291 | 0 | __LINE__, nLineNumber); |
1292 | 0 | return nullptr; |
1293 | 0 | } |
1294 | | |
1295 | 62.8k | CPLString osObjectName = aszTokenStack[--nTokenStackSize]; |
1296 | | |
1297 | 62.8k | if (osObjectName[0] != '/') |
1298 | 136 | { |
1299 | 136 | CPLDebug( |
1300 | 136 | "PDF", |
1301 | 136 | "Should not happen at line %d: offset %d in stream", |
1302 | 136 | __LINE__, int(pszContent - pszContentIni)); |
1303 | 136 | CPLDebug("PDF", |
1304 | 136 | "ParseContent(), line %d: return at line %d " |
1305 | 136 | "of content stream", |
1306 | 136 | __LINE__, nLineNumber); |
1307 | 136 | return nullptr; |
1308 | 136 | } |
1309 | | |
1310 | 62.7k | if (osObjectName.find("/SymImage") == 0) |
1311 | 121 | { |
1312 | 121 | oCoords.push_back(oGS.adfCM[4] + oGS.adfCM[0] / 2); |
1313 | 121 | oCoords.push_back(oGS.adfCM[5] + oGS.adfCM[3] / 2); |
1314 | | |
1315 | 121 | szToken[0] = '\0'; |
1316 | 121 | nTokenSize = 0; |
1317 | | |
1318 | 121 | if (poCurLayer != nullptr) |
1319 | 0 | bEmitFeature = TRUE; |
1320 | 121 | else |
1321 | 121 | continue; |
1322 | 121 | } |
1323 | 62.5k | else if (poResources == nullptr) |
1324 | 782 | { |
1325 | 782 | szToken[0] = '\0'; |
1326 | 782 | nTokenSize = 0; |
1327 | | |
1328 | 782 | CPLDebug("PDF", "Skipping unknown object %s at line %d", |
1329 | 782 | osObjectName.c_str(), nLineNumber); |
1330 | 782 | continue; |
1331 | 782 | } |
1332 | | |
1333 | 61.8k | if (!bEmitFeature) |
1334 | 61.8k | { |
1335 | 61.8k | GDALPDFObject *poXObject = |
1336 | 61.8k | poResources->GetDictionary()->Get("XObject"); |
1337 | 61.8k | if (poXObject == nullptr || |
1338 | 61.6k | poXObject->GetType() != PDFObjectType_Dictionary) |
1339 | 164 | { |
1340 | 164 | CPLDebug("PDF", |
1341 | 164 | "Should not happen at line %d: offset %d " |
1342 | 164 | "in stream", |
1343 | 164 | __LINE__, int(pszContent - pszContentIni)); |
1344 | 164 | CPLDebug("PDF", |
1345 | 164 | "ParseContent(), line %d: return at line " |
1346 | 164 | "%d of content stream", |
1347 | 164 | __LINE__, nLineNumber); |
1348 | 164 | return nullptr; |
1349 | 164 | } |
1350 | | |
1351 | 61.6k | GDALPDFObject *poObject = |
1352 | 61.6k | poXObject->GetDictionary()->Get( |
1353 | 61.6k | osObjectName.c_str() + 1); |
1354 | 61.6k | if (poObject == nullptr) |
1355 | 2.42k | { |
1356 | 2.42k | CPLDebug("PDF", |
1357 | 2.42k | "Should not happen at line %d: offset %d " |
1358 | 2.42k | "in stream", |
1359 | 2.42k | __LINE__, int(pszContent - pszContentIni)); |
1360 | 2.42k | CPLDebug("PDF", |
1361 | 2.42k | "ParseContent(), line %d: return at line " |
1362 | 2.42k | "%d of content stream", |
1363 | 2.42k | __LINE__, nLineNumber); |
1364 | 2.42k | return nullptr; |
1365 | 2.42k | } |
1366 | | |
1367 | 59.2k | int bParseStream = TRUE; |
1368 | 59.2k | GDALPDFObject *poSubResources = nullptr; |
1369 | | /* Check if the object is an image. If so, no need to |
1370 | | * try to parse */ |
1371 | | /* it. */ |
1372 | 59.2k | if (poObject->GetType() == PDFObjectType_Dictionary) |
1373 | 59.1k | { |
1374 | 59.1k | GDALPDFObject *poSubtype = |
1375 | 59.1k | poObject->GetDictionary()->Get("Subtype"); |
1376 | 59.1k | if (poSubtype != nullptr && |
1377 | 58.8k | poSubtype->GetType() == PDFObjectType_Name && |
1378 | 58.8k | poSubtype->GetName() == "Image") |
1379 | 14.3k | { |
1380 | 14.3k | bParseStream = FALSE; |
1381 | 14.3k | } |
1382 | | |
1383 | 59.1k | poSubResources = |
1384 | 59.1k | poObject->GetDictionary()->Get("Resources"); |
1385 | 59.1k | if (poSubResources && poSubResources->GetType() != |
1386 | 44.5k | PDFObjectType_Dictionary) |
1387 | 752 | { |
1388 | 752 | poSubResources = nullptr; |
1389 | 752 | } |
1390 | 59.1k | } |
1391 | | |
1392 | 59.2k | if (bParseStream) |
1393 | 44.8k | { |
1394 | 44.8k | GDALPDFStream *poStream = poObject->GetStream(); |
1395 | 44.8k | if (!poStream) |
1396 | 154 | { |
1397 | 154 | CPLDebug("PDF", |
1398 | 154 | "Should not happen at line %d: offset " |
1399 | 154 | "%d in stream", |
1400 | 154 | __LINE__, |
1401 | 154 | int(pszContent - pszContentIni)); |
1402 | 154 | CPLDebug("PDF", |
1403 | 154 | "ParseContent(), line %d: return at " |
1404 | 154 | "line %d of content stream", |
1405 | 154 | __LINE__, nLineNumber); |
1406 | 154 | return nullptr; |
1407 | 154 | } |
1408 | | |
1409 | 44.7k | OGRPDFLayer *poCurLayerRec = poCurLayer; |
1410 | | |
1411 | 44.7k | if (poObject->GetType() == PDFObjectType_Dictionary) |
1412 | 44.7k | { |
1413 | 44.7k | auto poOC = |
1414 | 44.7k | poObject->GetDictionary()->Get("OC"); |
1415 | 44.7k | if (poOC && |
1416 | 12 | poOC->GetType() == |
1417 | 12 | PDFObjectType_Dictionary && |
1418 | 12 | poOC->GetRefNum().toBool()) |
1419 | 12 | { |
1420 | 12 | const auto oIterNumGenToLayer = |
1421 | 12 | oMapNumGenToLayer.find( |
1422 | 12 | std::pair(poOC->GetRefNum().toInt(), |
1423 | 12 | poOC->GetRefGen())); |
1424 | 12 | if (oIterNumGenToLayer != |
1425 | 12 | oMapNumGenToLayer.end()) |
1426 | 9 | { |
1427 | 9 | poCurLayerRec = |
1428 | 9 | oIterNumGenToLayer->second; |
1429 | 9 | } |
1430 | 12 | } |
1431 | 44.7k | } |
1432 | | |
1433 | 44.7k | char *pszStr = poStream->GetBytes(); |
1434 | 44.7k | if (pszStr) |
1435 | 44.2k | { |
1436 | 44.2k | CPLDebug("PDF", "Starting parsing %s", |
1437 | 44.2k | osObjectName.c_str()); |
1438 | 44.2k | OGRGeometry *poGeom = ParseContent( |
1439 | 44.2k | pszStr, poSubResources, bCollectAllObjects, |
1440 | 44.2k | false, false, oMapPropertyToLayer, |
1441 | 44.2k | oMapNumGenToLayer, oGS, poCurLayerRec, |
1442 | 44.2k | nRecLevel + 1); |
1443 | 44.2k | CPLDebug("PDF", "End of parsing of %s", |
1444 | 44.2k | osObjectName.c_str()); |
1445 | 44.2k | CPLFree(pszStr); |
1446 | 44.2k | if (poGeom && !bCollectAllObjects) |
1447 | 17.4k | return poGeom; |
1448 | 26.7k | delete poGeom; |
1449 | 26.7k | } |
1450 | 44.7k | } |
1451 | 59.2k | } |
1452 | 61.8k | } |
1453 | 398k | else if (EQUAL2(szToken, "RG") || EQUAL2(szToken, "rg")) |
1454 | 93.9k | { |
1455 | 93.9k | double *padf = (EQUAL2(szToken, "RG")) |
1456 | 93.9k | ? &oGS.adfStrokeColor[0] |
1457 | 93.9k | : &oGS.adfFillColor[0]; |
1458 | 93.9k | if (!UnstackTokens(szToken, 3, aszTokenStack, |
1459 | 93.9k | nTokenStackSize, padf)) |
1460 | 72 | { |
1461 | 72 | CPLDebug( |
1462 | 72 | "PDF", |
1463 | 72 | "Should not happen at line %d: offset %d in stream", |
1464 | 72 | __LINE__, int(pszContent - pszContentIni)); |
1465 | 72 | CPLDebug("PDF", |
1466 | 72 | "ParseContent(), line %d: return at line %d " |
1467 | 72 | "of content stream", |
1468 | 72 | __LINE__, nLineNumber); |
1469 | 72 | return nullptr; |
1470 | 72 | } |
1471 | 93.9k | } |
1472 | 304k | else if (m_oMapOperators.find(szToken) != m_oMapOperators.end()) |
1473 | 285k | { |
1474 | 285k | int nArgs = m_oMapOperators[szToken]; |
1475 | 285k | if (nArgs < 0) |
1476 | 42 | { |
1477 | 177 | while (nTokenStackSize != 0) |
1478 | 135 | { |
1479 | 135 | CPLString osTopToken = |
1480 | 135 | aszTokenStack[--nTokenStackSize]; |
1481 | 135 | if (m_oMapOperators.find(osTopToken) != |
1482 | 135 | m_oMapOperators.end()) |
1483 | 0 | break; |
1484 | 135 | } |
1485 | 42 | } |
1486 | 285k | else |
1487 | 285k | { |
1488 | 285k | if (nArgs > nTokenStackSize) |
1489 | 4 | { |
1490 | 4 | CPLDebug("PDF", "not enough arguments for %s", |
1491 | 4 | szToken); |
1492 | 4 | CPLDebug("PDF", |
1493 | 4 | "ParseContent(), line %d: return at line " |
1494 | 4 | "%d of content stream", |
1495 | 4 | __LINE__, nLineNumber); |
1496 | 4 | return nullptr; |
1497 | 4 | } |
1498 | 285k | nTokenStackSize -= nArgs; |
1499 | 285k | } |
1500 | 285k | } |
1501 | 18.7k | else |
1502 | 18.7k | { |
1503 | 18.7k | PUSH(aszTokenStack, szToken, nTokenSize); |
1504 | 18.7k | } |
1505 | | |
1506 | 6.28M | if (bEmitFeature && poCurLayer != nullptr) |
1507 | 48.2k | { |
1508 | 48.2k | auto poGeom = std::unique_ptr<OGRGeometry>( |
1509 | 48.2k | BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart)); |
1510 | 48.2k | bHasFoundFill = FALSE; |
1511 | 48.2k | bHasMultiPart = FALSE; |
1512 | 48.2k | if (poGeom) |
1513 | 47.4k | { |
1514 | 47.4k | auto poFeature = std::make_unique<OGRFeature>( |
1515 | 47.4k | poCurLayer->GetLayerDefn()); |
1516 | 47.4k | if (m_bSetStyle) |
1517 | 47.4k | { |
1518 | 47.4k | OGRwkbGeometryType eType = |
1519 | 47.4k | wkbFlatten(poGeom->getGeometryType()); |
1520 | 47.4k | if (eType == wkbLineString || |
1521 | 14.1k | eType == wkbMultiLineString) |
1522 | 39.2k | { |
1523 | 39.2k | poFeature->SetStyleString(CPLSPrintf( |
1524 | 39.2k | "PEN(c:#%02X%02X%02X)", |
1525 | 39.2k | static_cast<int>( |
1526 | 39.2k | oGS.adfStrokeColor[0] * 255 + 0.5), |
1527 | 39.2k | static_cast<int>( |
1528 | 39.2k | oGS.adfStrokeColor[1] * 255 + 0.5), |
1529 | 39.2k | static_cast<int>( |
1530 | 39.2k | oGS.adfStrokeColor[2] * 255 + 0.5))); |
1531 | 39.2k | } |
1532 | 8.22k | else if (eType == wkbPolygon || |
1533 | 863 | eType == wkbMultiPolygon) |
1534 | 7.52k | { |
1535 | 7.52k | poFeature->SetStyleString(CPLSPrintf( |
1536 | 7.52k | "PEN(c:#%02X%02X%02X);BRUSH(fc:#%02X%02X%" |
1537 | 7.52k | "02X)", |
1538 | 7.52k | static_cast<int>( |
1539 | 7.52k | oGS.adfStrokeColor[0] * 255 + 0.5), |
1540 | 7.52k | static_cast<int>( |
1541 | 7.52k | oGS.adfStrokeColor[1] * 255 + 0.5), |
1542 | 7.52k | static_cast<int>( |
1543 | 7.52k | oGS.adfStrokeColor[2] * 255 + 0.5), |
1544 | 7.52k | static_cast<int>(oGS.adfFillColor[0] * 255 + |
1545 | 7.52k | 0.5), |
1546 | 7.52k | static_cast<int>(oGS.adfFillColor[1] * 255 + |
1547 | 7.52k | 0.5), |
1548 | 7.52k | static_cast<int>(oGS.adfFillColor[2] * 255 + |
1549 | 7.52k | 0.5))); |
1550 | 7.52k | } |
1551 | 47.4k | } |
1552 | 47.4k | poGeom->assignSpatialReference( |
1553 | 47.4k | poCurLayer->GetSpatialRef()); |
1554 | 47.4k | poFeature->SetGeometry(std::move(poGeom)); |
1555 | 47.4k | CPL_IGNORE_RET_VAL( |
1556 | 47.4k | poCurLayer->CreateFeature(std::move(poFeature))); |
1557 | 47.4k | } |
1558 | | |
1559 | 48.2k | oCoords.resize(0); |
1560 | 48.2k | } |
1561 | 6.28M | } |
1562 | | |
1563 | 7.63M | szToken[0] = '\0'; |
1564 | 7.63M | nTokenSize = 0; |
1565 | 7.63M | } |
1566 | 52.6M | } |
1567 | | |
1568 | 51.3k | CPLDebug("PDF", "ParseContent(): reached line %d", nLineNumber); |
1569 | 51.3k | if (!oGSStack.empty()) |
1570 | 412 | CPLDebug("PDF", "GSStack not empty"); |
1571 | | |
1572 | 51.3k | if (nTokenStackSize != 0) |
1573 | 1.33k | { |
1574 | 4.45k | while (nTokenStackSize != 0) |
1575 | 3.11k | { |
1576 | 3.11k | nTokenStackSize--; |
1577 | 3.11k | CPLDebug("PDF", "Remaining values in stack : %s", |
1578 | 3.11k | aszTokenStack[nTokenStackSize]); |
1579 | 3.11k | } |
1580 | 1.33k | return nullptr; |
1581 | 1.33k | } |
1582 | | |
1583 | 49.9k | if (bCollectAllObjects) |
1584 | 15.5k | return nullptr; |
1585 | | |
1586 | 34.4k | return BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart).release(); |
1587 | 49.9k | } |
1588 | | |
1589 | | /************************************************************************/ |
1590 | | /* BuildGeometry() */ |
1591 | | /************************************************************************/ |
1592 | | |
1593 | | std::unique_ptr<OGRGeometry> |
1594 | | PDFDataset::BuildGeometry(std::vector<double> &oCoords, int bHasFoundFill, |
1595 | | int bHasMultiPart) |
1596 | 82.6k | { |
1597 | 82.6k | std::unique_ptr<OGRGeometry> poGeom; |
1598 | | |
1599 | 82.6k | if (!oCoords.size()) |
1600 | 15.6k | return nullptr; |
1601 | | |
1602 | 67.0k | if (oCoords.size() == 2) |
1603 | 718 | { |
1604 | 718 | double X, Y; |
1605 | 718 | PDFCoordsToSRSCoords(oCoords[0], oCoords[1], X, Y); |
1606 | 718 | poGeom = std::make_unique<OGRPoint>(X, Y); |
1607 | 718 | } |
1608 | 66.3k | else if (!bHasFoundFill) |
1609 | 41.2k | { |
1610 | 41.2k | std::unique_ptr<OGRLineString> poLS; |
1611 | 41.2k | std::unique_ptr<OGRMultiLineString> poMLS; |
1612 | 41.2k | if (bHasMultiPart) |
1613 | 6.05k | { |
1614 | 6.05k | poMLS = std::make_unique<OGRMultiLineString>(); |
1615 | 6.05k | } |
1616 | | |
1617 | 214k | for (size_t i = 0; i < oCoords.size(); i += 2) |
1618 | 173k | { |
1619 | 173k | if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH) |
1620 | 44.0k | { |
1621 | 44.0k | if (poMLS && poLS && !poLS->IsEmpty()) |
1622 | 3.30k | poMLS->addGeometry(std::move(poLS)); |
1623 | 44.0k | poLS = std::make_unique<OGRLineString>(); |
1624 | 44.0k | } |
1625 | 129k | else if (oCoords[i] == CLOSE_SUBPATH && |
1626 | 506 | oCoords[i + 1] == CLOSE_SUBPATH) |
1627 | 506 | { |
1628 | 506 | if (poLS && poLS->getNumPoints() >= 2 && |
1629 | 453 | !(poLS->getX(0) == poLS->getX(poLS->getNumPoints() - 1) && |
1630 | 349 | poLS->getY(0) == poLS->getY(poLS->getNumPoints() - 1))) |
1631 | 161 | { |
1632 | 161 | poLS->addPoint(poLS->getX(0), poLS->getY(0)); |
1633 | 161 | } |
1634 | 506 | } |
1635 | 128k | else if (oCoords[i] == FILL_SUBPATH && |
1636 | 0 | oCoords[i + 1] == FILL_SUBPATH) |
1637 | 0 | { |
1638 | | /* Should not happen */ |
1639 | 0 | } |
1640 | 128k | else if (poLS) |
1641 | 127k | { |
1642 | 127k | double X, Y; |
1643 | 127k | PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y); |
1644 | | |
1645 | 127k | poLS->addPoint(X, Y); |
1646 | 127k | } |
1647 | 173k | } |
1648 | | |
1649 | | // Recognize points as written by GDAL (ogr-sym-2 : circle (not filled)) |
1650 | 41.2k | if (poLS != nullptr && poLS->getNumPoints() == 1 + BEZIER_STEPS * 4 && |
1651 | 2 | (poGeom = PDFGetCircleCenter(poLS.get())) != nullptr) |
1652 | 2 | { |
1653 | | // done |
1654 | 2 | } |
1655 | | |
1656 | | // Recognize points as written by GDAL (ogr-sym-4: square (not filled)) |
1657 | 41.2k | else if (poLS != nullptr && |
1658 | 40.6k | (poLS->getNumPoints() == 4 || poLS->getNumPoints() == 5) && |
1659 | 833 | (poGeom = PDFGetSquareCenter(poLS.get())) != nullptr) |
1660 | 23 | { |
1661 | | // done |
1662 | 23 | } |
1663 | | |
1664 | | // Recognize points as written by GDAL (ogr-sym-6: triangle (not |
1665 | | // filled)) |
1666 | 41.2k | else if (poLS != nullptr && |
1667 | 40.6k | (poLS->getNumPoints() == 3 || poLS->getNumPoints() == 4) && |
1668 | 30.0k | (poGeom = PDFGetTriangleCenter(poLS.get())) != nullptr) |
1669 | 630 | { |
1670 | | // done |
1671 | 630 | } |
1672 | | |
1673 | | // Recognize points as written by GDAL (ogr-sym-8: star (not filled)) |
1674 | 40.6k | else if (poLS != nullptr && |
1675 | 40.0k | (poLS->getNumPoints() == 10 || poLS->getNumPoints() == 11) && |
1676 | 3 | (poGeom = PDFGetStarCenter(poLS.get())) != nullptr) |
1677 | 0 | { |
1678 | | // done |
1679 | 0 | } |
1680 | 40.6k | else if (poMLS) |
1681 | 6.05k | { |
1682 | 6.05k | if (poLS && !poLS->IsEmpty()) |
1683 | 6.00k | poMLS->addGeometry(std::move(poLS)); |
1684 | | |
1685 | 6.05k | if (poMLS->getNumGeometries() == 2) |
1686 | 1.89k | { |
1687 | 1.89k | const OGRLineString *poLS1 = poMLS->getGeometryRef(0); |
1688 | 1.89k | const OGRLineString *poLS2 = poMLS->getGeometryRef(1); |
1689 | | |
1690 | | // Recognize points as written by GDAL (ogr-sym-0: cross (+) ). |
1691 | 1.89k | if (poLS1->getNumPoints() == 2 && poLS2->getNumPoints() == 2 && |
1692 | 132 | poLS1->getY(0) == poLS1->getY(1) && |
1693 | 0 | poLS2->getX(0) == poLS2->getX(1) && |
1694 | 0 | fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) - |
1695 | 0 | fabs(poLS2->getY(0) - poLS2->getY(1))) < EPSILON && |
1696 | 0 | fabs((poLS1->getX(0) + poLS1->getX(1)) / 2 - |
1697 | 0 | poLS2->getX(0)) < EPSILON && |
1698 | 0 | fabs((poLS2->getY(0) + poLS2->getY(1)) / 2 - |
1699 | 0 | poLS1->getY(0)) < EPSILON) |
1700 | 0 | { |
1701 | 0 | poGeom = std::make_unique<OGRPoint>(poLS2->getX(0), |
1702 | 0 | poLS1->getY(0)); |
1703 | 0 | } |
1704 | | // Recognize points as written by GDAL (ogr-sym-1: diagcross (X) ). |
1705 | 1.89k | else if (poLS1->getNumPoints() == 2 && |
1706 | 155 | poLS2->getNumPoints() == 2 && |
1707 | 132 | poLS1->getX(0) == poLS2->getX(0) && |
1708 | 123 | poLS1->getY(0) == poLS2->getY(1) && |
1709 | 74 | poLS1->getX(1) == poLS2->getX(1) && |
1710 | 63 | poLS1->getY(1) == poLS2->getY(0) && |
1711 | 62 | fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) - |
1712 | 62 | fabs(poLS1->getY(0) - poLS1->getY(1))) < EPSILON) |
1713 | 62 | { |
1714 | 62 | poGeom = std::make_unique<OGRPoint>( |
1715 | 62 | (poLS1->getX(0) + poLS1->getX(1)) / 2, |
1716 | 62 | (poLS1->getY(0) + poLS1->getY(1)) / 2); |
1717 | 62 | } |
1718 | 1.89k | } |
1719 | | |
1720 | 6.05k | if (!poGeom) |
1721 | 5.99k | poGeom = std::move(poMLS); |
1722 | 6.05k | } |
1723 | 34.5k | else if (poLS) |
1724 | 34.0k | { |
1725 | 34.0k | poGeom = std::move(poLS); |
1726 | 34.0k | } |
1727 | 41.2k | } |
1728 | 25.0k | else |
1729 | 25.0k | { |
1730 | 25.0k | std::unique_ptr<OGRLinearRing> poLS; |
1731 | 25.0k | std::vector<std::unique_ptr<OGRGeometry>> apoPolys; |
1732 | | |
1733 | 2.00M | for (size_t i = 0; i < oCoords.size(); i += 2) |
1734 | 1.98M | { |
1735 | 1.98M | if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH) |
1736 | 199k | { |
1737 | 199k | if (poLS && poLS->getNumPoints() >= 3) |
1738 | 148k | { |
1739 | 148k | auto poPoly = std::make_unique<OGRPolygon>(); |
1740 | 148k | poPoly->addRing(std::move(poLS)); |
1741 | 148k | apoPolys.push_back(std::move(poPoly)); |
1742 | 148k | } |
1743 | 199k | poLS = std::make_unique<OGRLinearRing>(); |
1744 | 199k | } |
1745 | 1.78M | else if ((oCoords[i] == CLOSE_SUBPATH && |
1746 | 34.2k | oCoords[i + 1] == CLOSE_SUBPATH) || |
1747 | 1.75M | (oCoords[i] == FILL_SUBPATH && |
1748 | 23.2k | oCoords[i + 1] == FILL_SUBPATH)) |
1749 | 57.5k | { |
1750 | 57.5k | if (poLS) |
1751 | 49.8k | { |
1752 | 49.8k | poLS->closeRings(); |
1753 | | |
1754 | 49.8k | std::unique_ptr<OGRPoint> poCenter; |
1755 | | |
1756 | 49.8k | if (apoPolys.empty() && poLS && |
1757 | 24.5k | poLS->getNumPoints() == 1 + BEZIER_STEPS * 4) |
1758 | 14.6k | { |
1759 | | // Recognize points as written by GDAL (ogr-sym-3 : |
1760 | | // circle (filled)) |
1761 | 14.6k | poCenter = PDFGetCircleCenter(poLS.get()); |
1762 | 14.6k | } |
1763 | | |
1764 | 49.8k | if (apoPolys.empty() && poCenter == nullptr && poLS && |
1765 | 14.2k | poLS->getNumPoints() == 5) |
1766 | 7.50k | { |
1767 | | // Recognize points as written by GDAL (ogr-sym-5: |
1768 | | // square (filled)) |
1769 | 7.50k | poCenter = PDFGetSquareCenter(poLS.get()); |
1770 | | |
1771 | | /* ESRI points */ |
1772 | 7.50k | if (poCenter == nullptr && oCoords.size() == 14 && |
1773 | 27 | poLS->getY(0) == poLS->getY(1) && |
1774 | 25 | poLS->getX(1) == poLS->getX(2) && |
1775 | 25 | poLS->getY(2) == poLS->getY(3) && |
1776 | 25 | poLS->getX(3) == poLS->getX(0)) |
1777 | 25 | { |
1778 | 25 | poCenter = std::make_unique<OGRPoint>( |
1779 | 25 | (poLS->getX(0) + poLS->getX(1)) / 2, |
1780 | 25 | (poLS->getY(0) + poLS->getY(2)) / 2); |
1781 | 25 | } |
1782 | 7.50k | } |
1783 | | // Recognize points as written by GDAL (ogr-sym-7: triangle |
1784 | | // (filled)) |
1785 | 42.3k | else if (apoPolys.empty() && poLS && |
1786 | 17.0k | poLS->getNumPoints() == 4) |
1787 | 135 | { |
1788 | 135 | poCenter = PDFGetTriangleCenter(poLS.get()); |
1789 | 135 | } |
1790 | | // Recognize points as written by GDAL (ogr-sym-9: star |
1791 | | // (filled)) |
1792 | 42.2k | else if (apoPolys.empty() && poLS && |
1793 | 16.9k | poLS->getNumPoints() == 11) |
1794 | 1 | { |
1795 | 1 | poCenter = PDFGetStarCenter(poLS.get()); |
1796 | 1 | } |
1797 | | |
1798 | 49.8k | if (poCenter) |
1799 | 10.3k | { |
1800 | 10.3k | poGeom = std::move(poCenter); |
1801 | 10.3k | break; |
1802 | 10.3k | } |
1803 | | |
1804 | 39.4k | if (poLS->getNumPoints() >= 3) |
1805 | 22.2k | { |
1806 | 22.2k | auto poPoly = std::make_unique<OGRPolygon>(); |
1807 | 22.2k | poPoly->addRing(std::move(poLS)); |
1808 | 22.2k | apoPolys.push_back(std::move(poPoly)); |
1809 | 22.2k | } |
1810 | 39.4k | poLS.reset(); |
1811 | 39.4k | } |
1812 | 57.5k | } |
1813 | 1.72M | else |
1814 | 1.72M | { |
1815 | 1.72M | if (poLS) |
1816 | 1.72M | { |
1817 | 1.72M | double X, Y; |
1818 | 1.72M | PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y); |
1819 | | |
1820 | 1.72M | poLS->addPoint(X, Y); |
1821 | 1.72M | } |
1822 | 1.72M | } |
1823 | 1.98M | } |
1824 | | |
1825 | 25.0k | if (apoPolys.size() == 2 && |
1826 | 187 | apoPolys[0]->toPolygon()->getNumInteriorRings() == 0 && |
1827 | 187 | apoPolys[1]->toPolygon()->getNumInteriorRings() == 0) |
1828 | 187 | { |
1829 | 187 | const auto poRing0 = apoPolys[0]->toPolygon()->getExteriorRing(); |
1830 | 187 | const auto poRing1 = apoPolys[1]->toPolygon()->getExteriorRing(); |
1831 | 187 | if (poRing0->Equals(poRing1)) |
1832 | 0 | { |
1833 | | /* Just keep one ring if they are identical */ |
1834 | 0 | apoPolys.resize(1); |
1835 | 0 | } |
1836 | 187 | } |
1837 | 25.0k | if (!apoPolys.empty()) |
1838 | 13.5k | { |
1839 | 13.5k | poGeom = OGRGeometryFactory::organizePolygons(apoPolys); |
1840 | 13.5k | } |
1841 | 25.0k | } |
1842 | | |
1843 | 67.0k | return poGeom; |
1844 | 82.6k | } |
1845 | | |
1846 | | /************************************************************************/ |
1847 | | /* ExploreContents() */ |
1848 | | /************************************************************************/ |
1849 | | |
1850 | | void PDFDataset::ExploreContents(GDALPDFObject *poObj, |
1851 | | GDALPDFObject *poResources, int nDepth, |
1852 | | int &nVisited, bool &bStop) |
1853 | 7.88k | { |
1854 | 7.88k | std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer; |
1855 | 7.88k | if (nDepth == 10 || nVisited == 1000) |
1856 | 2 | { |
1857 | 2 | CPLError(CE_Failure, CPLE_AppDefined, |
1858 | 2 | "ExploreContents(): too deep exploration or too many items"); |
1859 | 2 | bStop = true; |
1860 | 2 | return; |
1861 | 2 | } |
1862 | 7.88k | if (bStop) |
1863 | 0 | return; |
1864 | | |
1865 | 7.88k | if (poObj->GetType() == PDFObjectType_Array) |
1866 | 120 | { |
1867 | 120 | GDALPDFArray *poArray = poObj->GetArray(); |
1868 | 6.54k | for (int i = 0; i < poArray->GetLength(); i++) |
1869 | 6.42k | { |
1870 | 6.42k | GDALPDFObject *poSubObj = poArray->Get(i); |
1871 | 6.42k | if (poSubObj) |
1872 | 6.40k | { |
1873 | 6.40k | nVisited++; |
1874 | 6.40k | ExploreContents(poSubObj, poResources, nDepth + 1, nVisited, |
1875 | 6.40k | bStop); |
1876 | 6.40k | if (bStop) |
1877 | 5 | return; |
1878 | 6.40k | } |
1879 | 6.42k | } |
1880 | 120 | } |
1881 | | |
1882 | 7.87k | if (poObj->GetType() != PDFObjectType_Dictionary) |
1883 | 6.26k | return; |
1884 | | |
1885 | 1.61k | GDALPDFStream *poStream = poObj->GetStream(); |
1886 | 1.61k | if (!poStream) |
1887 | 123 | return; |
1888 | | |
1889 | 1.49k | char *pszStr = poStream->GetBytes(); |
1890 | 1.49k | if (!pszStr) |
1891 | 1 | return; |
1892 | | |
1893 | 1.49k | const char *pszMCID = pszStr; |
1894 | 30.8k | while ((pszMCID = strstr(pszMCID, "/MCID")) != nullptr) |
1895 | 29.3k | { |
1896 | 29.3k | const char *pszBDC = strstr(pszMCID, "BDC"); |
1897 | 29.3k | if (pszBDC) |
1898 | 29.3k | { |
1899 | | /* Hack for |
1900 | | * http://www.avenza.com/sites/default/files/spatialpdf/US_County_Populations.pdf |
1901 | | */ |
1902 | | /* FIXME: that logic is too fragile. */ |
1903 | 29.3k | const char *pszStartParsing = pszBDC; |
1904 | 29.3k | const char *pszAfterBDC = pszBDC + 3; |
1905 | 29.3k | bool bMatchQ = false; |
1906 | 58.6k | while (pszAfterBDC[0] == ' ' || pszAfterBDC[0] == '\r' || |
1907 | 58.6k | pszAfterBDC[0] == '\n') |
1908 | 29.3k | pszAfterBDC++; |
1909 | 29.3k | if (STARTS_WITH(pszAfterBDC, "0 0 m")) |
1910 | 5 | { |
1911 | 5 | const char *pszLastq = pszBDC; |
1912 | 185 | while (pszLastq > pszStr && *pszLastq != 'q') |
1913 | 180 | pszLastq--; |
1914 | | |
1915 | 5 | if (pszLastq > pszStr && *pszLastq == 'q' && |
1916 | 0 | (pszLastq[-1] == ' ' || pszLastq[-1] == '\r' || |
1917 | 0 | pszLastq[-1] == '\n') && |
1918 | 0 | (pszLastq[1] == ' ' || pszLastq[1] == '\r' || |
1919 | 0 | pszLastq[1] == '\n')) |
1920 | 0 | { |
1921 | 0 | pszStartParsing = pszLastq; |
1922 | 0 | bMatchQ = true; |
1923 | 0 | } |
1924 | 5 | } |
1925 | | |
1926 | 29.3k | const int nMCID = atoi(pszMCID + 6); |
1927 | 29.3k | if (GetGeometryFromMCID(nMCID) == nullptr) |
1928 | 29.0k | { |
1929 | 29.0k | auto poGeom = std::unique_ptr<OGRGeometry>(ParseContent( |
1930 | 29.0k | pszStartParsing, poResources, false, !bMatchQ, bMatchQ, |
1931 | 29.0k | oMapPropertyToLayer, {}, GraphicState(), nullptr, 0)); |
1932 | 29.0k | if (poGeom != nullptr) |
1933 | 17.9k | { |
1934 | | /* Save geometry in map */ |
1935 | 17.9k | m_oMapMCID[nMCID] = std::move(poGeom); |
1936 | 17.9k | } |
1937 | 29.0k | } |
1938 | 29.3k | } |
1939 | 29.3k | pszMCID += 5; |
1940 | 29.3k | } |
1941 | 1.49k | CPLFree(pszStr); |
1942 | 1.49k | } |
1943 | | |
1944 | | /************************************************************************/ |
1945 | | /* ExploreContentsNonStructured() */ |
1946 | | /************************************************************************/ |
1947 | | |
1948 | | void PDFDataset::ExploreContentsNonStructuredInternal( |
1949 | | GDALPDFObject *poContents, GDALPDFObject *poResources, |
1950 | | const std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer, |
1951 | | const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer, |
1952 | | OGRPDFLayer *poSingleLayer) |
1953 | 2.58k | { |
1954 | 2.58k | if (poContents->GetType() == PDFObjectType_Array) |
1955 | 565 | { |
1956 | 565 | GDALPDFArray *poArray = poContents->GetArray(); |
1957 | 565 | char *pszConcatStr = nullptr; |
1958 | 565 | size_t nConcatLen = 0; |
1959 | 1.31k | for (int i = 0; i < poArray->GetLength(); i++) |
1960 | 1.20k | { |
1961 | 1.20k | GDALPDFObject *poObj = poArray->Get(i); |
1962 | 1.20k | if (poObj == nullptr || |
1963 | 882 | poObj->GetType() != PDFObjectType_Dictionary) |
1964 | 377 | break; |
1965 | 823 | GDALPDFStream *poStream = poObj->GetStream(); |
1966 | 823 | if (!poStream) |
1967 | 10 | break; |
1968 | 813 | char *pszStr = poStream->GetBytes(); |
1969 | 813 | if (!pszStr) |
1970 | 67 | break; |
1971 | 746 | const size_t nLen = strlen(pszStr); |
1972 | 746 | char *pszConcatStrNew = static_cast<char *>( |
1973 | 746 | CPLRealloc(pszConcatStr, nConcatLen + nLen + 1)); |
1974 | 746 | if (pszConcatStrNew == nullptr) |
1975 | 0 | { |
1976 | 0 | CPLFree(pszStr); |
1977 | 0 | break; |
1978 | 0 | } |
1979 | 746 | pszConcatStr = pszConcatStrNew; |
1980 | 746 | memcpy(pszConcatStr + nConcatLen, pszStr, nLen + 1); |
1981 | 746 | nConcatLen += nLen; |
1982 | 746 | CPLFree(pszStr); |
1983 | 746 | } |
1984 | 565 | if (pszConcatStr) |
1985 | 153 | ParseContent(pszConcatStr, poResources, poResources != nullptr, |
1986 | 153 | false, false, oMapPropertyToLayer, oMapNumGenToLayer, |
1987 | 153 | GraphicState(), poSingleLayer, 0); |
1988 | 565 | CPLFree(pszConcatStr); |
1989 | 565 | return; |
1990 | 565 | } |
1991 | | |
1992 | 2.01k | if (poContents->GetType() != PDFObjectType_Dictionary) |
1993 | 0 | return; |
1994 | | |
1995 | 2.01k | GDALPDFStream *poStream = poContents->GetStream(); |
1996 | 2.01k | if (!poStream) |
1997 | 141 | return; |
1998 | | |
1999 | 1.87k | char *pszStr = poStream->GetBytes(); |
2000 | 1.87k | if (!pszStr) |
2001 | 45 | return; |
2002 | 1.83k | ParseContent(pszStr, poResources, poResources != nullptr, false, false, |
2003 | 1.83k | oMapPropertyToLayer, oMapNumGenToLayer, GraphicState(), |
2004 | 1.83k | poSingleLayer, 0); |
2005 | 1.83k | CPLFree(pszStr); |
2006 | 1.83k | } |
2007 | | |
2008 | | /************************************************************************/ |
2009 | | /* ExploreResourceProperty() */ |
2010 | | /************************************************************************/ |
2011 | | |
2012 | | static void ExploreResourceProperty( |
2013 | | const char *pszKey, GDALPDFObject *poObj, const std::string &osType, |
2014 | | const std::map<std::pair<int, int>, OGRPDFLayer *> &oMapNumGenToLayer, |
2015 | | std::map<CPLString, OGRPDFLayer *> &oMapPropertyToLayer, int nRecLevel) |
2016 | 122k | { |
2017 | 122k | if (nRecLevel == 2) |
2018 | 0 | return; |
2019 | | |
2020 | 122k | if (osType == "OCG" && poObj->GetRefNum().toBool()) |
2021 | 2.80k | { |
2022 | 2.80k | const auto oIterNumGenToLayer = oMapNumGenToLayer.find( |
2023 | 2.80k | std::pair(poObj->GetRefNum().toInt(), poObj->GetRefGen())); |
2024 | 2.80k | if (oIterNumGenToLayer != oMapNumGenToLayer.end()) |
2025 | 1.39k | { |
2026 | 1.39k | auto poLayer = oIterNumGenToLayer->second; |
2027 | | #ifdef DEBUG_VERBOSE |
2028 | | CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey, |
2029 | | poLayer->GetName()); |
2030 | | #endif |
2031 | 1.39k | oMapPropertyToLayer[pszKey] = poLayer; |
2032 | 1.39k | } |
2033 | 1.41k | else |
2034 | 1.41k | { |
2035 | 1.41k | CPLDebug("PDF", |
2036 | 1.41k | "Resource.Properties[%s] referencing " |
2037 | 1.41k | "OGC %d not tied with a layer", |
2038 | 1.41k | pszKey, poObj->GetRefNum().toInt()); |
2039 | 1.41k | } |
2040 | 2.80k | } |
2041 | 120k | else if (osType == "OCMD") |
2042 | 115k | { |
2043 | | // Optional Content Group Membership Dictionary |
2044 | | // Deal with constructs like |
2045 | | /* |
2046 | | Item[0] : MC0 |
2047 | | Type = dictionary, Num = 331, Gen = 0 |
2048 | | Item[0] : OCGs |
2049 | | Type = array |
2050 | | Item[0]: |
2051 | | Type = dictionary, Num = 251, Gen = 0 |
2052 | | Item[0] : Intent = View (name) |
2053 | | Item[1] : Name = Orthoimage (string) |
2054 | | Item[2] : Type = OCG (name) |
2055 | | Item[1]: |
2056 | | Type = dictionary, Num = 250, Gen = 0 |
2057 | | Item[0] : Intent = View (name) |
2058 | | Item[1] : Name = Images (string) |
2059 | | Item[2] : Type = OCG (name) |
2060 | | Item[1] : P = AllOn (name) |
2061 | | Item[2] : Type = OCMD (name) |
2062 | | */ |
2063 | | // where the OCG Orthoimage is actually a child |
2064 | | // of Images (which will be named Orthoimage.Images) |
2065 | | // In which case we only associate MC0 to |
2066 | | // Orthoimage.Images |
2067 | | // Cf https://github.com/OSGeo/gdal/issues/8372 |
2068 | | // and https://prd-tnm.s3.amazonaws.com/StagedProducts/Maps/USTopo/PDF/ID/ID_Big_Baldy_20200409_TM_geo.pdf |
2069 | 115k | auto poOCGs = poObj->GetDictionary()->Get("OCGs"); |
2070 | 115k | if (poOCGs && poOCGs->GetType() == PDFObjectType_Array) |
2071 | 114k | { |
2072 | 114k | auto poOCGsArray = poOCGs->GetArray(); |
2073 | 114k | const int nLength = poOCGsArray->GetLength(); |
2074 | 114k | size_t nMaxNameLength = 0; |
2075 | 114k | OGRPDFLayer *poCandidateLayer = nullptr; |
2076 | 114k | std::vector<std::string> aosLayerNames; |
2077 | 552k | for (int i = 0; i < nLength; ++i) |
2078 | 437k | { |
2079 | 437k | auto poOCG = poOCGsArray->Get(i); |
2080 | 437k | if (poOCG && poOCG->GetType() == PDFObjectType_Dictionary) |
2081 | 298k | { |
2082 | 298k | auto poP = poOCG->GetDictionary()->Get("P"); |
2083 | 298k | if (poP && poP->GetType() == PDFObjectType_Name) |
2084 | 0 | { |
2085 | | // Visibility Policy |
2086 | 0 | const auto &osP = poP->GetName(); |
2087 | 0 | if (osP != "AllOn" && osP != "AnyOn") |
2088 | 0 | { |
2089 | 0 | CPLDebug("PDF", |
2090 | 0 | "Resource.Properties[%s] " |
2091 | 0 | "has unhandled visibility policy %s", |
2092 | 0 | pszKey, osP.c_str()); |
2093 | 0 | } |
2094 | 0 | } |
2095 | 298k | auto poOCGType = poOCG->GetDictionary()->Get("Type"); |
2096 | 298k | if (poOCGType && poOCGType->GetType() == PDFObjectType_Name) |
2097 | 294k | { |
2098 | 294k | const std::string &osOCGType = poOCGType->GetName(); |
2099 | 294k | if (osOCGType == "OCG" && poOCG->GetRefNum().toBool()) |
2100 | 245k | { |
2101 | 245k | const auto oIterNumGenToLayer = |
2102 | 245k | oMapNumGenToLayer.find( |
2103 | 245k | std::pair(poOCG->GetRefNum().toInt(), |
2104 | 245k | poOCG->GetRefGen())); |
2105 | 245k | if (oIterNumGenToLayer != oMapNumGenToLayer.end()) |
2106 | 106k | { |
2107 | 106k | auto poLayer = oIterNumGenToLayer->second; |
2108 | 106k | aosLayerNames.emplace_back(poLayer->GetName()); |
2109 | 106k | if (strlen(poLayer->GetName()) > nMaxNameLength) |
2110 | 62.6k | { |
2111 | 62.6k | nMaxNameLength = strlen(poLayer->GetName()); |
2112 | 62.6k | poCandidateLayer = poLayer; |
2113 | 62.6k | } |
2114 | 106k | } |
2115 | 138k | else |
2116 | 138k | { |
2117 | 138k | CPLDebug("PDF", |
2118 | 138k | "Resource.Properties[%s][%d] " |
2119 | 138k | "referencing OGC %d not tied with " |
2120 | 138k | "a layer", |
2121 | 138k | pszKey, i, poOCG->GetRefNum().toInt()); |
2122 | 138k | } |
2123 | 245k | } |
2124 | 49.0k | else |
2125 | 49.0k | { |
2126 | 49.0k | CPLDebug( |
2127 | 49.0k | "PDF", |
2128 | 49.0k | "Resource.Properties[%s][%d] has unhandled " |
2129 | 49.0k | "Type member: %s", |
2130 | 49.0k | pszKey, i, osOCGType.c_str()); |
2131 | 49.0k | } |
2132 | 294k | } |
2133 | 298k | } |
2134 | 437k | } |
2135 | | |
2136 | 114k | if (!aosLayerNames.empty()) |
2137 | 62.3k | { |
2138 | | // Sort layer names and if each one starts |
2139 | | // with the previous ones, then the OCGs |
2140 | | // are part of a hierarchy, and we can |
2141 | | // associate the property name with the |
2142 | | // last one. |
2143 | 62.3k | std::sort(aosLayerNames.begin(), aosLayerNames.end()); |
2144 | 62.3k | bool bOK = true; |
2145 | 70.7k | for (size_t i = 1; i < aosLayerNames.size(); ++i) |
2146 | 8.59k | { |
2147 | 8.59k | if (aosLayerNames[i].find(aosLayerNames[i - 1]) != 0) |
2148 | 266 | { |
2149 | 266 | bOK = false; |
2150 | 266 | break; |
2151 | 266 | } |
2152 | 8.59k | } |
2153 | 62.3k | if (bOK) |
2154 | 62.1k | { |
2155 | 62.1k | CPLAssert(poCandidateLayer); |
2156 | | #ifdef DEBUG_VERBOSE |
2157 | | CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey, |
2158 | | poCandidateLayer->GetName()); |
2159 | | #endif |
2160 | 62.1k | oMapPropertyToLayer[pszKey] = poCandidateLayer; |
2161 | 62.1k | } |
2162 | 266 | else |
2163 | 266 | { |
2164 | 266 | CPLDebug("PDF", |
2165 | 266 | "Resource.Properties[%s] " |
2166 | 266 | "contains a OCMD that cannot " |
2167 | 266 | "be mapped to a single layer", |
2168 | 266 | pszKey); |
2169 | 266 | } |
2170 | 62.3k | } |
2171 | 51.8k | else |
2172 | 51.8k | { |
2173 | 51.8k | CPLDebug("PDF", |
2174 | 51.8k | "Resource.Properties[%s] contains " |
2175 | 51.8k | "a OCMD without OCGs", |
2176 | 51.8k | pszKey); |
2177 | 51.8k | } |
2178 | 114k | } |
2179 | 1.18k | else if (poOCGs && poOCGs->GetType() == PDFObjectType_Dictionary) |
2180 | 39 | { |
2181 | 39 | auto poOGGsType = poOCGs->GetDictionary()->Get("Type"); |
2182 | 39 | if (poOGGsType && poOGGsType->GetType() == PDFObjectType_Name) |
2183 | 38 | { |
2184 | 38 | ExploreResourceProperty(pszKey, poOCGs, poOGGsType->GetName(), |
2185 | 38 | oMapNumGenToLayer, oMapPropertyToLayer, |
2186 | 38 | nRecLevel + 1); |
2187 | 38 | } |
2188 | 1 | else |
2189 | 1 | { |
2190 | 1 | CPLDebug("PDF", |
2191 | 1 | "Resource.Properties[%s] contains a OGCs member with " |
2192 | 1 | "no Type member", |
2193 | 1 | pszKey); |
2194 | 1 | } |
2195 | 39 | } |
2196 | 1.14k | else if (poOCGs) |
2197 | 199 | { |
2198 | 199 | CPLDebug("PDF", |
2199 | 199 | "Resource.Properties[%s] contains a OCMD " |
2200 | 199 | "with a OGCs member of unhandled type: %s", |
2201 | 199 | pszKey, poOCGs->GetTypeName()); |
2202 | 199 | } |
2203 | 944 | else |
2204 | 944 | { |
2205 | | // Could have a VE (visibility expression) |
2206 | | // expression instead, but we don't handle that |
2207 | 944 | CPLDebug("PDF", |
2208 | 944 | "Resource.Properties[%s] contains a " |
2209 | 944 | "OCMD with a missing OGC (perhaps has a VE?)", |
2210 | 944 | pszKey); |
2211 | 944 | } |
2212 | 115k | } |
2213 | 4.72k | else |
2214 | 4.72k | { |
2215 | 4.72k | CPLDebug("PDF", |
2216 | 4.72k | "Resource.Properties[%s] has unhandled " |
2217 | 4.72k | "Type member: %s", |
2218 | 4.72k | pszKey, osType.c_str()); |
2219 | 4.72k | } |
2220 | 122k | } |
2221 | | |
2222 | | /************************************************************************/ |
2223 | | /* ExploreContentsNonStructured() */ |
2224 | | /************************************************************************/ |
2225 | | |
2226 | | void PDFDataset::ExploreContentsNonStructured(GDALPDFObject *poContents, |
2227 | | GDALPDFObject *poResources) |
2228 | 9.79k | { |
2229 | 9.79k | std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer; |
2230 | 9.79k | std::map<std::pair<int, int>, OGRPDFLayer *> oMapNumGenToLayer; |
2231 | | |
2232 | 9.79k | const auto BuildMapNumGenToLayer = [this, &oMapNumGenToLayer]() |
2233 | 9.79k | { |
2234 | 3.70k | for (const auto &oLayerWithref : m_aoLayerWithRef) |
2235 | 534k | { |
2236 | 534k | CPLString osSanitizedName( |
2237 | 534k | PDFSanitizeLayerName(oLayerWithref.osName)); |
2238 | | |
2239 | 534k | OGRPDFLayer *poPDFLayer = dynamic_cast<OGRPDFLayer *>( |
2240 | 534k | GetLayerByName(osSanitizedName.c_str())); |
2241 | 534k | if (!poPDFLayer) |
2242 | 54.3k | { |
2243 | 54.3k | auto poSRSOri = GetSpatialRef(); |
2244 | 54.3k | OGRSpatialReference *poSRS = |
2245 | 54.3k | poSRSOri ? poSRSOri->Clone() : nullptr; |
2246 | 54.3k | auto poPDFLayerUniquePtr = std::make_unique<OGRPDFLayer>( |
2247 | 54.3k | this, osSanitizedName.c_str(), poSRS, wkbUnknown); |
2248 | 54.3k | if (poSRS) |
2249 | 1.13k | poSRS->Release(); |
2250 | | |
2251 | 54.3k | m_apoLayers.emplace_back(std::move(poPDFLayerUniquePtr)); |
2252 | 54.3k | poPDFLayer = m_apoLayers.back().get(); |
2253 | 54.3k | } |
2254 | | |
2255 | 534k | oMapNumGenToLayer[std::pair(oLayerWithref.nOCGNum.toInt(), |
2256 | 534k | oLayerWithref.nOCGGen)] = poPDFLayer; |
2257 | 534k | } |
2258 | 3.70k | }; |
2259 | | |
2260 | 9.79k | if (poResources != nullptr && |
2261 | 9.79k | poResources->GetType() == PDFObjectType_Dictionary) |
2262 | 9.79k | { |
2263 | 9.79k | auto poResourcesDict = poResources->GetDictionary(); |
2264 | 9.79k | GDALPDFObject *poTopProperties = poResourcesDict->Get("Properties"); |
2265 | 9.79k | if (poTopProperties != nullptr && |
2266 | 3.71k | poTopProperties->GetType() == PDFObjectType_Dictionary) |
2267 | 3.67k | { |
2268 | 3.67k | BuildMapNumGenToLayer(); |
2269 | | |
2270 | 3.67k | for (const auto &[osKey, poObj] : |
2271 | 3.67k | poTopProperties->GetDictionary()->GetValues()) |
2272 | 132k | { |
2273 | 132k | const char *pszKey = osKey.c_str(); |
2274 | 132k | if (poObj->GetType() == PDFObjectType_Dictionary) |
2275 | 125k | { |
2276 | 125k | auto poType = poObj->GetDictionary()->Get("Type"); |
2277 | 125k | if (poType && poType->GetType() == PDFObjectType_Name) |
2278 | 122k | { |
2279 | 122k | const auto &osType = poType->GetName(); |
2280 | 122k | ExploreResourceProperty(pszKey, poObj, osType, |
2281 | 122k | oMapNumGenToLayer, |
2282 | 122k | oMapPropertyToLayer, 0); |
2283 | 122k | } |
2284 | 3.08k | else |
2285 | 3.08k | { |
2286 | 3.08k | CPLDebug("PDF", |
2287 | 3.08k | "Resource.Properties[%s] has no Type member", |
2288 | 3.08k | pszKey); |
2289 | 3.08k | } |
2290 | 125k | } |
2291 | 132k | } |
2292 | 3.67k | } |
2293 | 6.11k | else |
2294 | 6.11k | { |
2295 | | // Code path taken for datasets mentioned at https://github.com/OSGeo/gdal/issues/9870 |
2296 | | // generated by ArcGIS 12.9 |
2297 | 6.11k | const auto poXObject = poResourcesDict->Get("XObject"); |
2298 | 6.11k | if (poXObject && poXObject->GetType() == PDFObjectType_Dictionary) |
2299 | 2.26k | { |
2300 | 2.26k | for (const auto &oNameObjectPair : |
2301 | 2.26k | poXObject->GetDictionary()->GetValues()) |
2302 | 7.18k | { |
2303 | 7.18k | const auto poProperties = |
2304 | 7.18k | oNameObjectPair.second->LookupObject( |
2305 | 7.18k | "Resources.Properties"); |
2306 | 7.18k | if (poProperties && |
2307 | 28 | poProperties->GetType() == PDFObjectType_Dictionary) |
2308 | 26 | { |
2309 | 26 | BuildMapNumGenToLayer(); |
2310 | | |
2311 | 26 | const auto &oMap = |
2312 | 26 | poProperties->GetDictionary()->GetValues(); |
2313 | 26 | for (const auto &[osKey, poObj] : oMap) |
2314 | 43 | { |
2315 | 43 | const char *pszKey = osKey.c_str(); |
2316 | 43 | if (poObj->GetType() == PDFObjectType_Dictionary) |
2317 | 43 | { |
2318 | 43 | GDALPDFObject *poType = |
2319 | 43 | poObj->GetDictionary()->Get("Type"); |
2320 | 43 | if (poType && |
2321 | 33 | poType->GetType() == PDFObjectType_Name) |
2322 | 33 | { |
2323 | 33 | const auto &osType = poType->GetName(); |
2324 | 33 | ExploreResourceProperty( |
2325 | 33 | pszKey, poObj, osType, |
2326 | 33 | oMapNumGenToLayer, oMapPropertyToLayer, |
2327 | 33 | 0); |
2328 | 33 | } |
2329 | 43 | } |
2330 | 43 | } |
2331 | | |
2332 | 26 | break; |
2333 | 26 | } |
2334 | 7.18k | } |
2335 | 2.26k | } |
2336 | 6.11k | } |
2337 | 9.79k | } |
2338 | | |
2339 | 9.79k | OGRPDFLayer *poSingleLayer = nullptr; |
2340 | 9.79k | if (m_apoLayers.empty()) |
2341 | 7.20k | { |
2342 | 7.20k | const char *pszReadNonStructured = |
2343 | 7.20k | CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", nullptr); |
2344 | 7.20k | if (pszReadNonStructured && CPLTestBool(pszReadNonStructured)) |
2345 | 0 | { |
2346 | 0 | auto poLayer = std::make_unique<OGRPDFLayer>(this, "content", |
2347 | 0 | nullptr, wkbUnknown); |
2348 | 0 | m_apoLayers.emplace_back(std::move(poLayer)); |
2349 | 0 | poSingleLayer = m_apoLayers.back().get(); |
2350 | 0 | } |
2351 | 7.20k | else |
2352 | 7.20k | { |
2353 | 7.20k | if (!pszReadNonStructured) |
2354 | 7.20k | { |
2355 | 7.20k | CPLDebug( |
2356 | 7.20k | "PDF", |
2357 | 7.20k | "No structured content nor PDF layers detected, hence " |
2358 | 7.20k | "vector content detection is disabled. You may force " |
2359 | 7.20k | "vector content detection by setting the " |
2360 | 7.20k | "OGR_PDF_READ_NON_STRUCTURED configuration option to YES"); |
2361 | 7.20k | } |
2362 | 7.20k | return; |
2363 | 7.20k | } |
2364 | 7.20k | } |
2365 | | |
2366 | 2.58k | ExploreContentsNonStructuredInternal(poContents, poResources, |
2367 | 2.58k | oMapPropertyToLayer, oMapNumGenToLayer, |
2368 | 2.58k | poSingleLayer); |
2369 | | |
2370 | | /* Remove empty layers */ |
2371 | 56.9k | for (auto oIter = m_apoLayers.begin(); oIter != m_apoLayers.end(); |
2372 | 2.58k | /* do nothing */) |
2373 | 54.3k | { |
2374 | 54.3k | if ((*oIter)->GetFeatureCount(false) == 0) |
2375 | 53.7k | { |
2376 | 53.7k | oIter = m_apoLayers.erase(oIter); |
2377 | 53.7k | } |
2378 | 661 | else |
2379 | 661 | { |
2380 | 661 | ++oIter; |
2381 | 661 | } |
2382 | 54.3k | } |
2383 | 2.58k | } |
2384 | | |
2385 | | #endif /* HAVE_PDF_READ_SUPPORT */ |