Line data Source code
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 : #define SQUARE(x) ((x) * (x))
19 : #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 85761 : bool PDFDataset::OpenVectorLayers(GDALPDFDictionary *poPageDict)
32 : {
33 85761 : if (m_bHasLoadedLayers)
34 85739 : return true;
35 22 : m_bHasLoadedLayers = true;
36 :
37 22 : if (poPageDict == nullptr)
38 : {
39 9 : poPageDict = m_poPageObj->GetDictionary();
40 9 : if (poPageDict == nullptr)
41 0 : return false;
42 : }
43 :
44 22 : GetCatalog();
45 44 : if (m_poCatalogObject == nullptr ||
46 22 : m_poCatalogObject->GetType() != PDFObjectType_Dictionary)
47 0 : return false;
48 :
49 22 : GDALPDFObject *poContents = poPageDict->Get("Contents");
50 22 : if (poContents == nullptr)
51 0 : return false;
52 :
53 23 : if (poContents->GetType() != PDFObjectType_Dictionary &&
54 1 : poContents->GetType() != PDFObjectType_Array)
55 0 : return false;
56 :
57 22 : GDALPDFObject *poResources = poPageDict->Get("Resources");
58 44 : if (poResources == nullptr ||
59 22 : poResources->GetType() != PDFObjectType_Dictionary)
60 0 : return false;
61 :
62 : GDALPDFObject *poStructTreeRoot =
63 22 : m_poCatalogObject->GetDictionary()->Get("StructTreeRoot");
64 42 : if (CPLTestBool(CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", "NO")) ||
65 42 : poStructTreeRoot == nullptr ||
66 5 : poStructTreeRoot->GetType() != PDFObjectType_Dictionary)
67 : {
68 17 : ExploreContentsNonStructured(poContents, poResources);
69 : }
70 : else
71 : {
72 : bool bHasFeatures;
73 : {
74 5 : std::set<std::pair<int, int>> aoSetAlreadyVisited;
75 5 : bHasFeatures = ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0,
76 : /* bDryRun = */ true);
77 : }
78 5 : if (bHasFeatures)
79 : {
80 5 : int nDepth = 0;
81 5 : int nVisited = 0;
82 5 : bool bStop = false;
83 5 : ExploreContents(poContents, poResources, nDepth, nVisited, bStop);
84 10 : std::set<std::pair<int, int>> aoSetAlreadyVisited;
85 5 : ExploreTree(poStructTreeRoot, aoSetAlreadyVisited, 0,
86 : /* bDryRun = */ false);
87 : }
88 : else
89 : {
90 0 : ExploreContentsNonStructured(poContents, poResources);
91 : }
92 : }
93 :
94 22 : CleanupIntermediateResources();
95 :
96 22 : bool bEmptyDS = true;
97 22 : for (auto &poLayer : m_apoLayers)
98 : {
99 15 : if (poLayer->GetFeatureCount(false) != 0)
100 : {
101 15 : bEmptyDS = false;
102 15 : break;
103 : }
104 : }
105 22 : return !bEmptyDS;
106 : }
107 :
108 : /************************************************************************/
109 : /* CleanupIntermediateResources() */
110 : /************************************************************************/
111 :
112 241 : void PDFDataset::CleanupIntermediateResources()
113 : {
114 241 : m_oMapMCID.clear();
115 241 : }
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 219 : void PDFDataset::InitMapOperators()
204 : {
205 14016 : for (const auto &sPDFOperator : asPDFOperators)
206 13797 : m_oMapOperators[sPDFOperator.szOpName] = sPDFOperator.nArgs;
207 219 : }
208 :
209 : /************************************************************************/
210 : /* TestCapability() */
211 : /************************************************************************/
212 :
213 0 : int PDFDataset::TestCapability(const char *) const
214 : {
215 0 : return FALSE;
216 : }
217 :
218 : /************************************************************************/
219 : /* GetLayer() */
220 : /************************************************************************/
221 :
222 50526 : const OGRLayer *PDFDataset::GetLayer(int iLayer) const
223 :
224 : {
225 50526 : const_cast<PDFDataset *>(this)->OpenVectorLayers(nullptr);
226 50526 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
227 0 : return nullptr;
228 :
229 50526 : return m_apoLayers[iLayer].get();
230 : }
231 :
232 : /************************************************************************/
233 : /* GetLayerCount() */
234 : /************************************************************************/
235 :
236 35222 : int PDFDataset::GetLayerCount() const
237 : {
238 35222 : const_cast<PDFDataset *>(this)->OpenVectorLayers(nullptr);
239 35222 : return static_cast<int>(m_apoLayers.size());
240 : }
241 :
242 : /************************************************************************/
243 : /* ExploreTree() */
244 : /************************************************************************/
245 :
246 20 : bool PDFDataset::ExploreTree(GDALPDFObject *poObj,
247 : std::set<std::pair<int, int>> &aoSetAlreadyVisited,
248 : int nRecLevel, bool bDryRun)
249 : {
250 20 : if (nRecLevel == 16)
251 0 : return false;
252 :
253 20 : std::pair<int, int> oObjPair(poObj->GetRefNum().toInt(),
254 40 : poObj->GetRefGen());
255 20 : if (aoSetAlreadyVisited.find(oObjPair) != aoSetAlreadyVisited.end())
256 0 : return false;
257 20 : aoSetAlreadyVisited.insert(oObjPair);
258 :
259 20 : if (poObj->GetType() != PDFObjectType_Dictionary)
260 0 : return false;
261 :
262 20 : GDALPDFDictionary *poDict = poObj->GetDictionary();
263 :
264 20 : GDALPDFObject *poS = poDict->Get("S");
265 40 : std::string osS;
266 20 : if (poS != nullptr && poS->GetType() == PDFObjectType_Name)
267 : {
268 10 : osS = poS->GetName();
269 : }
270 :
271 20 : GDALPDFObject *poT = poDict->Get("T");
272 40 : std::string osT;
273 20 : if (poT != nullptr && poT->GetType() == PDFObjectType_String)
274 : {
275 10 : osT = poT->GetString();
276 : }
277 :
278 20 : GDALPDFObject *poK = poDict->Get("K");
279 20 : if (poK == nullptr)
280 0 : return false;
281 :
282 20 : bool bRet = false;
283 20 : if (poK->GetType() == PDFObjectType_Array)
284 : {
285 20 : GDALPDFArray *poArray = poK->GetArray();
286 : GDALPDFObject *poElt0;
287 : GDALPDFObject *poElt0_K;
288 40 : if (poArray->GetLength() > 0 && (poElt0 = poArray->Get(0)) &&
289 20 : poElt0->GetType() == PDFObjectType_Dictionary &&
290 60 : (poElt0_K = poElt0->GetDictionary()->Get("K")) &&
291 20 : poElt0_K->GetType() == PDFObjectType_Int)
292 : {
293 10 : if (bDryRun)
294 : {
295 5 : for (int i = 0; i < poArray->GetLength(); i++)
296 : {
297 5 : auto poFeatureObj = poArray->Get(i);
298 10 : if (poFeatureObj &&
299 5 : poFeatureObj->GetType() == PDFObjectType_Dictionary)
300 : {
301 5 : auto poA = poFeatureObj->GetDictionary()->Get("A");
302 5 : if (poA && poA->GetType() == PDFObjectType_Dictionary)
303 : {
304 5 : auto poO = poA->GetDictionary()->Get("O");
305 10 : if (poO && poO->GetType() == PDFObjectType_Name &&
306 5 : poO->GetName() == "UserProperties")
307 : {
308 5 : return true;
309 : }
310 : }
311 : }
312 : }
313 0 : return false;
314 : }
315 :
316 10 : std::string osLayerName;
317 5 : if (!osT.empty())
318 5 : osLayerName = std::move(osT);
319 : else
320 : {
321 0 : if (!osS.empty())
322 0 : osLayerName = std::move(osS);
323 : else
324 : osLayerName = CPLSPrintf(
325 0 : "Layer%d", static_cast<int>(m_apoLayers.size()) + 1);
326 : }
327 :
328 5 : auto poSRSOri = GetSpatialRef();
329 5 : OGRSpatialReference *poSRS = poSRSOri ? poSRSOri->Clone() : nullptr;
330 : auto poLayer = std::make_unique<OGRPDFLayer>(
331 5 : this, osLayerName.c_str(), poSRS, wkbUnknown);
332 5 : if (poSRS)
333 5 : poSRS->Release();
334 :
335 5 : poLayer->Fill(poArray);
336 :
337 5 : m_apoLayers.emplace_back(std::move(poLayer));
338 5 : bRet = true;
339 : }
340 : else
341 : {
342 15 : for (int i = 0; i < poArray->GetLength(); i++)
343 : {
344 10 : auto poSubObj = poArray->Get(i);
345 10 : if (poSubObj)
346 : {
347 10 : if (ExploreTree(poSubObj, aoSetAlreadyVisited,
348 10 : nRecLevel + 1, bDryRun) &&
349 : bDryRun)
350 5 : return true;
351 : }
352 : }
353 : }
354 : }
355 0 : else if (poK->GetType() == PDFObjectType_Dictionary)
356 : {
357 0 : if (ExploreTree(poK, aoSetAlreadyVisited, nRecLevel + 1, bDryRun) &&
358 : bDryRun)
359 0 : return true;
360 : }
361 :
362 10 : return bRet;
363 : }
364 :
365 : /************************************************************************/
366 : /* GetGeometryFromMCID() */
367 : /************************************************************************/
368 :
369 79 : OGRGeometry *PDFDataset::GetGeometryFromMCID(int nMCID)
370 : {
371 79 : auto oMapIter = m_oMapMCID.find(nMCID);
372 79 : if (oMapIter != m_oMapMCID.end())
373 38 : return oMapIter->second.get();
374 : else
375 41 : return nullptr;
376 : }
377 :
378 : /************************************************************************/
379 : /* GraphicState::PreMultiplyBy() */
380 : /************************************************************************/
381 :
382 21721 : void PDFDataset::GraphicState::PreMultiplyBy(double adfMatrix[6])
383 : {
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 21721 : double a = adfMatrix[0];
398 21721 : double b = adfMatrix[1];
399 21721 : double c = adfMatrix[2];
400 21721 : double d = adfMatrix[3];
401 21721 : double e = adfMatrix[4];
402 21721 : double f = adfMatrix[5];
403 21721 : double ap = adfCM[0];
404 21721 : double bp = adfCM[1];
405 21721 : double cp = adfCM[2];
406 21721 : double dp = adfCM[3];
407 21721 : double ep = adfCM[4];
408 21721 : double fp = adfCM[5];
409 21721 : adfCM[0] = a * ap + b * cp;
410 21721 : adfCM[1] = a * bp + b * dp;
411 21721 : adfCM[2] = c * ap + d * cp;
412 21721 : adfCM[3] = c * bp + d * dp;
413 21721 : adfCM[4] = e * ap + f * cp + ep;
414 21721 : adfCM[5] = e * bp + f * dp + fp;
415 21721 : }
416 :
417 : /************************************************************************/
418 : /* GraphicState::ApplyMatrix() */
419 : /************************************************************************/
420 :
421 6618820 : void PDFDataset::GraphicState::ApplyMatrix(double adfCoords[2]) const
422 : {
423 6618820 : double x = adfCoords[0];
424 6618820 : double y = adfCoords[1];
425 :
426 6618820 : adfCoords[0] = x * adfCM[0] + y * adfCM[2] + adfCM[4];
427 6618820 : adfCoords[1] = x * adfCM[1] + y * adfCM[3] + adfCM[5];
428 6618820 : }
429 :
430 : /************************************************************************/
431 : /* PDFCoordsToSRSCoords() */
432 : /************************************************************************/
433 :
434 12589600 : void PDFDataset::PDFCoordsToSRSCoords(double x, double y, double &X, double &Y)
435 : {
436 12589600 : x = x / m_dfPageWidth * nRasterXSize;
437 12589600 : if (m_bGeoTransformValid)
438 12588100 : y = (1 - y / m_dfPageHeight) * nRasterYSize;
439 : else
440 1496 : y = (y / m_dfPageHeight) * nRasterYSize;
441 :
442 12589600 : X = m_gt[0] + x * m_gt[1] + y * m_gt[2];
443 12589600 : Y = m_gt[3] + x * m_gt[4] + y * m_gt[5];
444 :
445 12589600 : if (fabs(X - std::round(X)) < 1e-8)
446 78 : X = std::round(X);
447 12589600 : if (fabs(Y - std::round(Y)) < 1e-8)
448 73 : Y = std::round(Y);
449 12589600 : }
450 :
451 : /************************************************************************/
452 : /* PDFGetCircleCenter() */
453 : /************************************************************************/
454 :
455 : /* Return the center of a circle, or NULL if it is not recognized */
456 :
457 2483 : static std::unique_ptr<OGRPoint> PDFGetCircleCenter(OGRLineString *poLS)
458 : {
459 2483 : if (poLS == nullptr || poLS->getNumPoints() != 1 + 4 * BEZIER_STEPS)
460 0 : return nullptr;
461 :
462 2497 : if (poLS->getY(0 * BEZIER_STEPS) == poLS->getY(2 * BEZIER_STEPS) &&
463 14 : poLS->getX(1 * BEZIER_STEPS) == poLS->getX(3 * BEZIER_STEPS) &&
464 28 : fabs((poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2 -
465 2511 : poLS->getX(1 * BEZIER_STEPS)) < EPSILON &&
466 28 : fabs((poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2 -
467 14 : poLS->getY(0 * BEZIER_STEPS)) < EPSILON)
468 : {
469 : return std::make_unique<OGRPoint>(
470 14 : (poLS->getX(0 * BEZIER_STEPS) + poLS->getX(2 * BEZIER_STEPS)) / 2,
471 42 : (poLS->getY(1 * BEZIER_STEPS) + poLS->getY(3 * BEZIER_STEPS)) / 2);
472 : }
473 2469 : return nullptr;
474 : }
475 :
476 : /************************************************************************/
477 : /* PDFGetSquareCenter() */
478 : /************************************************************************/
479 :
480 : /* Return the center of a square, or NULL if it is not recognized */
481 :
482 93438 : static std::unique_ptr<OGRPoint> PDFGetSquareCenter(OGRLineString *poLS)
483 : {
484 93438 : if (poLS == nullptr || poLS->getNumPoints() < 4 || poLS->getNumPoints() > 5)
485 0 : return nullptr;
486 :
487 95039 : if (poLS->getX(0) == poLS->getX(3) && poLS->getY(0) == poLS->getY(1) &&
488 95048 : poLS->getX(1) == poLS->getX(2) && poLS->getY(2) == poLS->getY(3) &&
489 9 : fabs(fabs(poLS->getX(0) - poLS->getX(1)) -
490 9 : fabs(poLS->getY(0) - poLS->getY(3))) < EPSILON)
491 : {
492 8 : return std::make_unique<OGRPoint>((poLS->getX(0) + poLS->getX(1)) / 2,
493 24 : (poLS->getY(0) + poLS->getY(3)) / 2);
494 : }
495 93430 : return nullptr;
496 : }
497 :
498 : /************************************************************************/
499 : /* PDFGetTriangleCenter() */
500 : /************************************************************************/
501 :
502 : /* Return the center of a equilateral triangle, or NULL if it is not recognized
503 : */
504 :
505 15507 : static std::unique_ptr<OGRPoint> PDFGetTriangleCenter(OGRLineString *poLS)
506 : {
507 15507 : if (poLS == nullptr || poLS->getNumPoints() < 3 || poLS->getNumPoints() > 4)
508 0 : return nullptr;
509 :
510 15507 : double dfSqD1 = SQUARE(poLS->getX(0) - poLS->getX(1)) +
511 15507 : SQUARE(poLS->getY(0) - poLS->getY(1));
512 15507 : double dfSqD2 = SQUARE(poLS->getX(1) - poLS->getX(2)) +
513 15507 : SQUARE(poLS->getY(1) - poLS->getY(2));
514 15507 : double dfSqD3 = SQUARE(poLS->getX(0) - poLS->getX(2)) +
515 15507 : SQUARE(poLS->getY(0) - poLS->getY(2));
516 15507 : if (fabs(dfSqD1 - dfSqD2) < EPSILON && fabs(dfSqD2 - dfSqD3) < EPSILON)
517 : {
518 : return std::make_unique<OGRPoint>(
519 8 : (poLS->getX(0) + poLS->getX(1) + poLS->getX(2)) / 3,
520 24 : (poLS->getY(0) + poLS->getY(1) + poLS->getY(2)) / 3);
521 : }
522 15499 : return nullptr;
523 : }
524 :
525 : /************************************************************************/
526 : /* PDFGetStarCenter() */
527 : /************************************************************************/
528 :
529 : /* Return the center of a 5-point star, or NULL if it is not recognized */
530 :
531 14537 : static std::unique_ptr<OGRPoint> PDFGetStarCenter(OGRLineString *poLS)
532 : {
533 29074 : if (poLS == nullptr || poLS->getNumPoints() < 10 ||
534 14537 : poLS->getNumPoints() > 11)
535 0 : return nullptr;
536 :
537 14537 : double dfSqD01 = SQUARE(poLS->getX(0) - poLS->getX(1)) +
538 14537 : SQUARE(poLS->getY(0) - poLS->getY(1));
539 14537 : double dfSqD02 = SQUARE(poLS->getX(0) - poLS->getX(2)) +
540 14537 : SQUARE(poLS->getY(0) - poLS->getY(2));
541 14537 : double dfSqD13 = SQUARE(poLS->getX(1) - poLS->getX(3)) +
542 14537 : SQUARE(poLS->getY(1) - poLS->getY(3));
543 14537 : const double dfSin18divSin126 = 0.38196601125;
544 14537 : if (dfSqD02 == 0)
545 3 : return nullptr;
546 14534 : int bOK = fabs(dfSqD13 / dfSqD02 - SQUARE(dfSin18divSin126)) < EPSILON;
547 14606 : for (int i = 1; i < 10 && bOK; i++)
548 : {
549 72 : double dfSqDiip1 = SQUARE(poLS->getX(i) - poLS->getX((i + 1) % 10)) +
550 72 : SQUARE(poLS->getY(i) - poLS->getY((i + 1) % 10));
551 72 : if (fabs(dfSqDiip1 - dfSqD01) > EPSILON)
552 : {
553 0 : bOK = FALSE;
554 : }
555 72 : double dfSqDiip2 = SQUARE(poLS->getX(i) - poLS->getX((i + 2) % 10)) +
556 72 : SQUARE(poLS->getY(i) - poLS->getY((i + 2) % 10));
557 72 : if ((i % 2) == 1 && fabs(dfSqDiip2 - dfSqD13) > EPSILON)
558 : {
559 0 : bOK = FALSE;
560 : }
561 72 : if ((i % 2) == 0 && fabs(dfSqDiip2 - dfSqD02) > EPSILON)
562 : {
563 0 : bOK = FALSE;
564 : }
565 : }
566 14534 : if (bOK)
567 : {
568 : return std::make_unique<OGRPoint>(
569 8 : (poLS->getX(0) + poLS->getX(2) + poLS->getX(4) + poLS->getX(6) +
570 8 : poLS->getX(8)) /
571 : 5,
572 8 : (poLS->getY(0) + poLS->getY(2) + poLS->getY(4) + poLS->getY(6) +
573 16 : poLS->getY(8)) /
574 8 : 5);
575 : }
576 14526 : return nullptr;
577 : }
578 :
579 : /************************************************************************/
580 : /* UnstackTokens() */
581 : /************************************************************************/
582 :
583 4934790 : int PDFDataset::UnstackTokens(
584 : const char *pszToken, int nRequiredArgs,
585 : char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE], int &nTokenStackSize,
586 : double *adfCoords)
587 : {
588 4934790 : if (nTokenStackSize < nRequiredArgs)
589 : {
590 0 : CPLDebug("PDF", "not enough arguments for %s", pszToken);
591 0 : return FALSE;
592 : }
593 4934790 : nTokenStackSize -= nRequiredArgs;
594 18304000 : for (int i = 0; i < nRequiredArgs; i++)
595 : {
596 13369200 : adfCoords[i] = CPLAtof(aszTokenStack[nTokenStackSize + i]);
597 : }
598 4934790 : return TRUE;
599 : }
600 :
601 : /************************************************************************/
602 : /* AddBezierCurve() */
603 : /************************************************************************/
604 :
605 853077 : 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 : {
609 853077 : double x0 = x0_y0[0];
610 853077 : double y0 = x0_y0[1];
611 853077 : double x1 = x1_y1[0];
612 853077 : double y1 = x1_y1[1];
613 853077 : double x2 = x2_y2[0];
614 853077 : double y2 = x2_y2[1];
615 853077 : double x3 = x3_y3[0];
616 853077 : double y3 = x3_y3[1];
617 8530770 : for (int i = 1; i < BEZIER_STEPS; i++)
618 : {
619 7677690 : const double t = static_cast<double>(i) / BEZIER_STEPS;
620 7677690 : const double t2 = t * t;
621 7677690 : const double t3 = t2 * t;
622 7677690 : const double oneMinust = 1 - t;
623 7677690 : const double oneMinust2 = oneMinust * oneMinust;
624 7677690 : const double oneMinust3 = oneMinust2 * oneMinust;
625 7677690 : const double three_t_oneMinust = 3 * t * oneMinust;
626 7677690 : const double x = oneMinust3 * x0 +
627 7677690 : three_t_oneMinust * (oneMinust * x1 + t * x2) +
628 7677690 : t3 * x3;
629 7677690 : const double y = oneMinust3 * y0 +
630 7677690 : three_t_oneMinust * (oneMinust * y1 + t * y2) +
631 7677690 : t3 * y3;
632 7677690 : oCoords.push_back(x);
633 7677690 : oCoords.push_back(y);
634 : }
635 853077 : oCoords.push_back(x3);
636 853077 : oCoords.push_back(y3);
637 853077 : }
638 :
639 : /************************************************************************/
640 : /* ParseContent() */
641 : /************************************************************************/
642 :
643 : #define NEW_SUBPATH -99
644 : #define CLOSE_SUBPATH -98
645 : #define FILL_SUBPATH -97
646 :
647 195 : 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 : {
654 195 : if (nRecLevel == 32)
655 : {
656 0 : CPLError(CE_Failure, CPLE_AppDefined,
657 : "Too many recursion levels in ParseContent()");
658 0 : return nullptr;
659 : }
660 195 : if (CPLTestBool(CPLGetConfigOption("PDF_DUMP_CONTENT", "NO")))
661 : {
662 : 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 : }
668 195 : const char *pszContentIni = pszContent;
669 : #ifdef DEBUG_VERBOSE
670 : CPLDebug("PDF", "Initial layer: %s",
671 : poCurLayer ? poCurLayer->GetName() : "(null)");
672 : #endif
673 :
674 : #define PUSH(aszTokenStack, str, strlen) \
675 : do \
676 : { \
677 : if (nTokenStackSize < TOKEN_STACK_SIZE) \
678 : memcpy(aszTokenStack[nTokenStackSize++], str, strlen + 1); \
679 : else \
680 : { \
681 : CPLError(CE_Failure, CPLE_AppDefined, \
682 : "Max token stack size reached"); \
683 : return nullptr; \
684 : }; \
685 : } while (false)
686 :
687 : #define ADD_CHAR(szToken, c) \
688 : do \
689 : { \
690 : if (nTokenSize < MAX_TOKEN_SIZE - 1) \
691 : { \
692 : szToken[nTokenSize++] = c; \
693 : szToken[nTokenSize] = '\0'; \
694 : } \
695 : else \
696 : { \
697 : CPLError(CE_Failure, CPLE_AppDefined, "Max token size reached"); \
698 : return nullptr; \
699 : }; \
700 : } while (false)
701 :
702 : char szToken[MAX_TOKEN_SIZE];
703 195 : int nTokenSize = 0;
704 : char ch;
705 : char aszTokenStack[TOKEN_STACK_SIZE][MAX_TOKEN_SIZE];
706 195 : int nTokenStackSize = 0;
707 195 : int bInString = FALSE;
708 195 : int nBDCOrBMCLevel = 0;
709 195 : int nParenthesisLevel = 0;
710 195 : int nArrayLevel = 0;
711 195 : int nBTLevel = 0;
712 :
713 195 : GraphicState oGS(graphicStateIn);
714 390 : std::stack<GraphicState> oGSStack;
715 390 : std::stack<OGRPDFLayer *> oLayerStack;
716 :
717 390 : std::vector<double> oCoords;
718 195 : int bHasFoundFill = FALSE;
719 195 : int bHasMultiPart = FALSE;
720 :
721 195 : szToken[0] = '\0';
722 :
723 195 : if (bInitBDCStack)
724 : {
725 39 : PUSH(aszTokenStack, "dummy", 5);
726 39 : PUSH(aszTokenStack, "dummy", 5);
727 39 : oLayerStack.push(nullptr);
728 : }
729 :
730 195 : int nLineNumber = 0;
731 :
732 122680000 : while ((ch = *pszContent) != '\0')
733 : {
734 122680000 : int bPushToken = FALSE;
735 :
736 122680000 : if (!bInString && ch == '%')
737 : {
738 : /* Skip comments until end-of-line */
739 52 : while ((ch = *pszContent) != '\0')
740 : {
741 52 : if (ch == '\r' || ch == '\n')
742 : break;
743 48 : pszContent++;
744 : }
745 4 : if (ch == 0)
746 0 : break;
747 4 : ++nLineNumber;
748 4 : if (ch == '\r' && pszContent[1] == '\n')
749 : {
750 0 : ++pszContent;
751 : }
752 : }
753 122680000 : else if (!bInString && (ch == ' ' || ch == '\r' || ch == '\n'))
754 : {
755 20527900 : if (ch == '\r')
756 : {
757 3518 : ++nLineNumber;
758 3518 : if (pszContent[1] == '\n')
759 : {
760 3518 : ++pszContent;
761 : }
762 : }
763 20524400 : else if (ch == '\n')
764 6026960 : ++nLineNumber;
765 20527900 : bPushToken = TRUE;
766 : }
767 :
768 : /* Ignore arrays */
769 102152000 : else if (!bInString && nTokenSize == 0 && ch == '[')
770 : {
771 275878 : nArrayLevel++;
772 : }
773 101876000 : else if (!bInString && nArrayLevel && ch == ']')
774 : {
775 275878 : nArrayLevel--;
776 275878 : nTokenSize = 0; // completely ignore content in arrays
777 : }
778 :
779 101600000 : else if (!bInString && nTokenSize == 0 && ch == '(')
780 : {
781 1678 : bInString = TRUE;
782 1678 : nParenthesisLevel++;
783 1678 : ADD_CHAR(szToken, ch);
784 : }
785 101599000 : else if (bInString && ch == '(')
786 : {
787 0 : nParenthesisLevel++;
788 0 : ADD_CHAR(szToken, ch);
789 : }
790 101599000 : else if (bInString && ch == ')')
791 : {
792 1678 : nParenthesisLevel--;
793 1678 : ADD_CHAR(szToken, ch);
794 1678 : if (nParenthesisLevel == 0)
795 : {
796 1678 : bInString = FALSE;
797 1678 : bPushToken = TRUE;
798 : }
799 : }
800 101597000 : else if (bInString && ch == '\\')
801 : {
802 96 : const auto nextCh = pszContent[1];
803 96 : if (nextCh == 'n')
804 : {
805 0 : ADD_CHAR(szToken, '\n');
806 0 : pszContent++;
807 : }
808 96 : else if (nextCh == 'r')
809 : {
810 0 : ADD_CHAR(szToken, '\r');
811 0 : pszContent++;
812 : }
813 96 : else if (nextCh == 't')
814 : {
815 0 : ADD_CHAR(szToken, '\t');
816 0 : pszContent++;
817 : }
818 96 : else if (nextCh == 'b')
819 : {
820 0 : ADD_CHAR(szToken, '\b');
821 0 : pszContent++;
822 : }
823 96 : else if (nextCh == '(' || nextCh == ')' || nextCh == '\\')
824 : {
825 0 : ADD_CHAR(szToken, nextCh);
826 0 : pszContent++;
827 : }
828 96 : else if (nextCh >= '0' && nextCh <= '7' && pszContent[2] >= '0' &&
829 96 : pszContent[2] <= '7' && pszContent[3] >= '0' &&
830 96 : pszContent[3] <= '7')
831 : {
832 96 : ADD_CHAR(szToken,
833 : ((nextCh - '\0') * 64 + (pszContent[2] - '\0') * 8 +
834 : pszContent[3] - '\0'));
835 96 : pszContent += 3;
836 : }
837 0 : else if (nextCh == '\n')
838 : {
839 0 : if (pszContent[2] == '\r')
840 0 : pszContent += 2;
841 : else
842 0 : pszContent++;
843 : }
844 0 : else if (nextCh == '\r')
845 : {
846 0 : pszContent++;
847 96 : }
848 : }
849 101597000 : else if (ch == '<' && pszContent[1] == '<' && nTokenSize == 0)
850 : {
851 1 : int nDictDepth = 0;
852 :
853 9 : while (*pszContent != '\0')
854 : {
855 9 : if (pszContent[0] == '<' && pszContent[1] == '<')
856 : {
857 1 : ADD_CHAR(szToken, '<');
858 1 : ADD_CHAR(szToken, '<');
859 1 : nDictDepth++;
860 1 : pszContent += 2;
861 : }
862 8 : else if (pszContent[0] == '>' && pszContent[1] == '>')
863 : {
864 1 : ADD_CHAR(szToken, '>');
865 1 : ADD_CHAR(szToken, '>');
866 1 : nDictDepth--;
867 1 : pszContent += 2;
868 1 : if (nDictDepth == 0)
869 1 : break;
870 : }
871 : else
872 : {
873 7 : ADD_CHAR(szToken, *pszContent);
874 7 : pszContent++;
875 : }
876 : }
877 1 : if (nDictDepth == 0)
878 : {
879 1 : bPushToken = TRUE;
880 1 : pszContent--;
881 : }
882 : else
883 1 : break;
884 : }
885 : else
886 : {
887 : // Do not create too long tokens in arrays, that we will ignore
888 : // anyway
889 101597000 : if (nArrayLevel == 0 || nTokenSize == 0)
890 : {
891 100904000 : ADD_CHAR(szToken, ch);
892 : }
893 : }
894 :
895 122680000 : pszContent++;
896 122680000 : if (pszContent[0] == '\0')
897 156 : bPushToken = TRUE;
898 :
899 : #define EQUAL1(szToken, s) (szToken[0] == s[0] && szToken[1] == '\0')
900 : #define EQUAL2(szToken, s) \
901 : (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == '\0')
902 : #define EQUAL3(szToken, s) \
903 : (szToken[0] == s[0] && szToken[1] == s[1] && szToken[2] == s[2] && \
904 : szToken[3] == '\0')
905 :
906 122680000 : if (bPushToken && nTokenSize)
907 : {
908 19984700 : if (EQUAL2(szToken, "BI"))
909 : {
910 0 : while (*pszContent != '\0')
911 : {
912 0 : if (pszContent[0] == 'E' && pszContent[1] == 'I' &&
913 0 : pszContent[2] == ' ')
914 : {
915 0 : break;
916 : }
917 0 : pszContent++;
918 : }
919 0 : if (pszContent[0] == 'E')
920 0 : pszContent += 3;
921 : else
922 : {
923 0 : CPLDebug("PDF",
924 : "ParseContent(), line %d: return at line %d of "
925 : "content stream",
926 : __LINE__, nLineNumber);
927 0 : return nullptr;
928 : }
929 : }
930 19984700 : else if (EQUAL3(szToken, "BDC"))
931 : {
932 257 : if (nTokenStackSize < 2)
933 : {
934 0 : CPLDebug("PDF", "not enough arguments for %s", szToken);
935 0 : CPLDebug("PDF",
936 : "ParseContent(), line %d: return at line %d of "
937 : "content stream",
938 : __LINE__, nLineNumber);
939 0 : return nullptr;
940 : }
941 257 : nTokenStackSize -= 2;
942 257 : const char *pszOC = aszTokenStack[nTokenStackSize];
943 257 : const char *pszOCGName = aszTokenStack[nTokenStackSize + 1];
944 :
945 257 : nBDCOrBMCLevel++;
946 :
947 257 : if (EQUAL3(pszOC, "/OC") && pszOCGName[0] == '/')
948 : {
949 217 : const auto oIter = oMapPropertyToLayer.find(pszOCGName + 1);
950 217 : if (oIter != oMapPropertyToLayer.end())
951 : {
952 156 : poCurLayer = oIter->second;
953 : }
954 : }
955 : #ifdef DEBUG_VERBOSE
956 : CPLDebug("PDF", "%s %s BDC -> Cur layer : %s", pszOC,
957 : pszOCGName,
958 : poCurLayer ? poCurLayer->GetName() : "(null)");
959 : #endif
960 257 : oLayerStack.push(poCurLayer);
961 : }
962 19984500 : else if (EQUAL3(szToken, "BMC"))
963 : {
964 0 : if (nTokenStackSize < 1)
965 : {
966 0 : CPLDebug("PDF", "not enough arguments for %s", szToken);
967 0 : CPLDebug("PDF",
968 : "ParseContent(), line %d: return at line %d of "
969 : "content stream",
970 : __LINE__, nLineNumber);
971 0 : return nullptr;
972 : }
973 0 : nTokenStackSize -= 1;
974 :
975 0 : nBDCOrBMCLevel++;
976 0 : oLayerStack.push(poCurLayer);
977 : }
978 19984500 : else if (EQUAL3(szToken, "EMC"))
979 : {
980 : // CPLDebug("PDF", "EMC");
981 220 : if (!oLayerStack.empty())
982 : {
983 220 : oLayerStack.pop();
984 220 : if (!oLayerStack.empty())
985 194 : poCurLayer = oLayerStack.top();
986 : else
987 26 : poCurLayer = nullptr;
988 :
989 : #ifdef DEBUG_VERBOSE
990 : CPLDebug("PDF", "EMC -> Cur layer : %s",
991 : poCurLayer ? poCurLayer->GetName() : "(null)");
992 : #endif
993 : }
994 : else
995 : {
996 0 : CPLDebug(
997 : "PDF",
998 : "Should not happen at line %d: offset %d in stream",
999 0 : __LINE__, int(pszContent - pszContentIni));
1000 0 : poCurLayer = nullptr;
1001 : // return NULL;
1002 : }
1003 :
1004 220 : nBDCOrBMCLevel--;
1005 220 : if (nBDCOrBMCLevel == 0 && bInitBDCStack)
1006 2 : break;
1007 : }
1008 :
1009 : /* Ignore any text stuff */
1010 19984200 : else if (EQUAL2(szToken, "BT"))
1011 8874 : nBTLevel++;
1012 19975400 : else if (EQUAL2(szToken, "ET"))
1013 : {
1014 8874 : nBTLevel--;
1015 8874 : if (nBTLevel < 0)
1016 : {
1017 0 : CPLDebug(
1018 : "PDF",
1019 : "Should not happen at line %d: offset %d in stream",
1020 0 : __LINE__, int(pszContent - pszContentIni));
1021 0 : CPLDebug("PDF",
1022 : "ParseContent(), line %d: return at line %d of "
1023 : "content stream",
1024 : __LINE__, nLineNumber);
1025 0 : return nullptr;
1026 : }
1027 : }
1028 19966500 : else if (!nArrayLevel && !nBTLevel)
1029 : {
1030 19589100 : int bEmitFeature = FALSE;
1031 :
1032 19589100 : if (szToken[0] < 'A')
1033 : {
1034 13690800 : PUSH(aszTokenStack, szToken, nTokenSize);
1035 : }
1036 5898320 : else if (EQUAL1(szToken, "q"))
1037 : {
1038 341 : oGSStack.push(oGS);
1039 : }
1040 5897980 : else if (EQUAL1(szToken, "Q"))
1041 : {
1042 341 : if (oGSStack.empty())
1043 : {
1044 0 : CPLDebug("PDF", "not enough arguments for %s", szToken);
1045 0 : CPLDebug("PDF",
1046 : "ParseContent(), line %d: return at line %d "
1047 : "of content stream",
1048 : __LINE__, nLineNumber);
1049 0 : return nullptr;
1050 : }
1051 :
1052 341 : oGS = oGSStack.top();
1053 341 : oGSStack.pop();
1054 :
1055 341 : if (oGSStack.empty() && bMatchQ)
1056 0 : break;
1057 : }
1058 5897640 : else if (EQUAL2(szToken, "cm"))
1059 : {
1060 : double adfMatrix[6];
1061 21721 : if (!UnstackTokens(szToken, 6, aszTokenStack,
1062 : nTokenStackSize, adfMatrix))
1063 : {
1064 0 : CPLDebug(
1065 : "PDF",
1066 : "Should not happen at line %d: offset %d in stream",
1067 0 : __LINE__, int(pszContent - pszContentIni));
1068 0 : CPLDebug("PDF",
1069 : "ParseContent(), line %d: return at line %d "
1070 : "of content stream",
1071 : __LINE__, nLineNumber);
1072 0 : return nullptr;
1073 : }
1074 :
1075 21721 : oGS.PreMultiplyBy(adfMatrix);
1076 : }
1077 5875910 : else if (EQUAL1(szToken, "b") || /* closepath, fill, stroke */
1078 5875910 : EQUAL2(szToken, "b*") /* closepath, eofill, stroke */)
1079 : {
1080 64 : if (!(!oCoords.empty() &&
1081 32 : oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
1082 10 : oCoords.back() == CLOSE_SUBPATH))
1083 : {
1084 22 : oCoords.push_back(CLOSE_SUBPATH);
1085 22 : oCoords.push_back(CLOSE_SUBPATH);
1086 : }
1087 32 : oCoords.push_back(FILL_SUBPATH);
1088 32 : oCoords.push_back(FILL_SUBPATH);
1089 32 : bHasFoundFill = TRUE;
1090 :
1091 32 : bEmitFeature = TRUE;
1092 : }
1093 5875880 : else if (EQUAL1(szToken, "B") || /* fill, stroke */
1094 5875880 : EQUAL2(szToken, "B*") || /* eofill, stroke */
1095 5875520 : EQUAL1(szToken, "f") || /* fill */
1096 5848280 : EQUAL1(szToken, "F") || /* fill */
1097 5848280 : EQUAL2(szToken, "f*") /* eofill */)
1098 : {
1099 47366 : oCoords.push_back(FILL_SUBPATH);
1100 47366 : oCoords.push_back(FILL_SUBPATH);
1101 47366 : bHasFoundFill = TRUE;
1102 :
1103 47366 : bEmitFeature = TRUE;
1104 : }
1105 5828520 : else if (EQUAL1(szToken, "h")) /* close subpath */
1106 : {
1107 680850 : if (!(!oCoords.empty() &&
1108 340425 : oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
1109 0 : oCoords.back() == CLOSE_SUBPATH))
1110 : {
1111 340425 : oCoords.push_back(CLOSE_SUBPATH);
1112 340425 : oCoords.push_back(CLOSE_SUBPATH);
1113 : }
1114 : }
1115 5488090 : else if (EQUAL1(
1116 : szToken,
1117 : "n")) /* new subpath without stroking or filling */
1118 : {
1119 176 : oCoords.resize(0);
1120 : }
1121 5487920 : else if (EQUAL1(szToken, "s")) /* close and stroke */
1122 : {
1123 32 : if (!(!oCoords.empty() &&
1124 16 : oCoords[oCoords.size() - 2] == CLOSE_SUBPATH &&
1125 0 : oCoords.back() == CLOSE_SUBPATH))
1126 : {
1127 16 : oCoords.push_back(CLOSE_SUBPATH);
1128 16 : oCoords.push_back(CLOSE_SUBPATH);
1129 : }
1130 :
1131 16 : bEmitFeature = TRUE;
1132 : }
1133 5487900 : else if (EQUAL1(szToken, "S")) /* stroke */
1134 : {
1135 273089 : bEmitFeature = TRUE;
1136 : }
1137 5214810 : else if (EQUAL1(szToken, "m") || EQUAL1(szToken, "l"))
1138 : {
1139 : double adfCoords[2];
1140 4059570 : if (!UnstackTokens(szToken, 2, aszTokenStack,
1141 : nTokenStackSize, adfCoords))
1142 : {
1143 0 : CPLDebug(
1144 : "PDF",
1145 : "Should not happen at line %d: offset %d in stream",
1146 0 : __LINE__, int(pszContent - pszContentIni));
1147 0 : CPLDebug("PDF",
1148 : "ParseContent(), line %d: return at line %d "
1149 : "of content stream",
1150 : __LINE__, nLineNumber);
1151 0 : return nullptr;
1152 : }
1153 :
1154 4059570 : if (EQUAL1(szToken, "m"))
1155 : {
1156 385486 : if (!oCoords.empty())
1157 64816 : bHasMultiPart = TRUE;
1158 385486 : oCoords.push_back(NEW_SUBPATH);
1159 385486 : oCoords.push_back(NEW_SUBPATH);
1160 : }
1161 :
1162 4059570 : oGS.ApplyMatrix(adfCoords);
1163 4059570 : oCoords.push_back(adfCoords[0]);
1164 4059570 : oCoords.push_back(adfCoords[1]);
1165 : }
1166 1155240 : else if (EQUAL1(szToken, "c")) /* Bezier curve */
1167 : {
1168 : double adfCoords[6];
1169 853077 : if (!UnstackTokens(szToken, 6, aszTokenStack,
1170 : nTokenStackSize, adfCoords))
1171 : {
1172 0 : CPLDebug(
1173 : "PDF",
1174 : "Should not happen at line %d: offset %d in stream",
1175 0 : __LINE__, int(pszContent - pszContentIni));
1176 0 : CPLDebug("PDF",
1177 : "ParseContent(), line %d: return at line %d "
1178 : "of content stream",
1179 : __LINE__, nLineNumber);
1180 0 : return nullptr;
1181 : }
1182 :
1183 853077 : oGS.ApplyMatrix(adfCoords + 0);
1184 853077 : oGS.ApplyMatrix(adfCoords + 2);
1185 853077 : oGS.ApplyMatrix(adfCoords + 4);
1186 1706150 : AddBezierCurve(oCoords,
1187 853077 : oCoords.empty()
1188 : ? &adfCoords[0]
1189 853077 : : &oCoords[oCoords.size() - 2],
1190 853077 : &adfCoords[0], &adfCoords[2], &adfCoords[4]);
1191 : }
1192 302161 : else if (EQUAL1(szToken, "v")) /* Bezier curve */
1193 : {
1194 : double adfCoords[4];
1195 0 : if (!UnstackTokens(szToken, 4, aszTokenStack,
1196 : nTokenStackSize, adfCoords))
1197 : {
1198 0 : CPLDebug(
1199 : "PDF",
1200 : "Should not happen at line %d: offset %d in stream",
1201 0 : __LINE__, int(pszContent - pszContentIni));
1202 0 : CPLDebug("PDF",
1203 : "ParseContent(), line %d: return at line %d "
1204 : "of content stream",
1205 : __LINE__, nLineNumber);
1206 0 : return nullptr;
1207 : }
1208 :
1209 0 : oGS.ApplyMatrix(adfCoords + 0);
1210 0 : oGS.ApplyMatrix(adfCoords + 2);
1211 0 : AddBezierCurve(
1212 : oCoords,
1213 0 : oCoords.empty() ? &adfCoords[0]
1214 0 : : &oCoords[oCoords.size() - 2],
1215 0 : oCoords.empty() ? &adfCoords[0]
1216 0 : : &oCoords[oCoords.size() - 2],
1217 0 : &adfCoords[0], &adfCoords[2]);
1218 : }
1219 302161 : else if (EQUAL1(szToken, "y")) /* Bezier curve */
1220 : {
1221 : double adfCoords[4];
1222 0 : if (!UnstackTokens(szToken, 4, aszTokenStack,
1223 : nTokenStackSize, adfCoords))
1224 : {
1225 0 : CPLDebug(
1226 : "PDF",
1227 : "Should not happen at line %d: offset %d in stream",
1228 0 : __LINE__, int(pszContent - pszContentIni));
1229 0 : CPLDebug("PDF",
1230 : "ParseContent(), line %d: return at line %d "
1231 : "of content stream",
1232 : __LINE__, nLineNumber);
1233 0 : return nullptr;
1234 : }
1235 :
1236 0 : oGS.ApplyMatrix(adfCoords + 0);
1237 0 : oGS.ApplyMatrix(adfCoords + 2);
1238 0 : AddBezierCurve(oCoords,
1239 0 : oCoords.empty()
1240 : ? &adfCoords[0]
1241 0 : : &oCoords[oCoords.size() - 2],
1242 0 : &adfCoords[0], &adfCoords[2], &adfCoords[2]);
1243 : }
1244 302161 : else if (EQUAL2(szToken, "re")) /* Rectangle */
1245 : {
1246 : double adfCoords[4];
1247 7 : if (!UnstackTokens(szToken, 4, aszTokenStack,
1248 : nTokenStackSize, adfCoords))
1249 : {
1250 0 : CPLDebug(
1251 : "PDF",
1252 : "Should not happen at line %d: offset %d in stream",
1253 0 : __LINE__, int(pszContent - pszContentIni));
1254 0 : CPLDebug("PDF",
1255 : "ParseContent(), line %d: return at line %d "
1256 : "of content stream",
1257 : __LINE__, nLineNumber);
1258 0 : return nullptr;
1259 : }
1260 :
1261 7 : adfCoords[2] += adfCoords[0];
1262 7 : adfCoords[3] += adfCoords[1];
1263 :
1264 7 : oGS.ApplyMatrix(adfCoords);
1265 7 : oGS.ApplyMatrix(adfCoords + 2);
1266 :
1267 7 : if (!oCoords.empty())
1268 0 : bHasMultiPart = TRUE;
1269 7 : oCoords.push_back(NEW_SUBPATH);
1270 7 : oCoords.push_back(NEW_SUBPATH);
1271 7 : oCoords.push_back(adfCoords[0]);
1272 7 : oCoords.push_back(adfCoords[1]);
1273 7 : oCoords.push_back(adfCoords[2]);
1274 7 : oCoords.push_back(adfCoords[1]);
1275 7 : oCoords.push_back(adfCoords[2]);
1276 7 : oCoords.push_back(adfCoords[3]);
1277 7 : oCoords.push_back(adfCoords[0]);
1278 7 : oCoords.push_back(adfCoords[3]);
1279 7 : oCoords.push_back(CLOSE_SUBPATH);
1280 7 : oCoords.push_back(CLOSE_SUBPATH);
1281 : }
1282 :
1283 302154 : else if (EQUAL2(szToken, "Do"))
1284 : {
1285 153 : if (nTokenStackSize == 0)
1286 : {
1287 0 : CPLDebug("PDF", "not enough arguments for %s", szToken);
1288 0 : CPLDebug("PDF",
1289 : "ParseContent(), line %d: return at line %d "
1290 : "of content stream",
1291 : __LINE__, nLineNumber);
1292 37 : return nullptr;
1293 : }
1294 :
1295 153 : CPLString osObjectName = aszTokenStack[--nTokenStackSize];
1296 :
1297 153 : if (osObjectName[0] != '/')
1298 : {
1299 0 : CPLDebug(
1300 : "PDF",
1301 : "Should not happen at line %d: offset %d in stream",
1302 0 : __LINE__, int(pszContent - pszContentIni));
1303 0 : CPLDebug("PDF",
1304 : "ParseContent(), line %d: return at line %d "
1305 : "of content stream",
1306 : __LINE__, nLineNumber);
1307 0 : return nullptr;
1308 : }
1309 :
1310 153 : if (osObjectName.find("/SymImage") == 0)
1311 : {
1312 4 : oCoords.push_back(oGS.adfCM[4] + oGS.adfCM[0] / 2);
1313 4 : oCoords.push_back(oGS.adfCM[5] + oGS.adfCM[3] / 2);
1314 :
1315 4 : szToken[0] = '\0';
1316 4 : nTokenSize = 0;
1317 :
1318 4 : if (poCurLayer != nullptr)
1319 2 : bEmitFeature = TRUE;
1320 : else
1321 2 : continue;
1322 : }
1323 149 : else if (poResources == nullptr)
1324 : {
1325 0 : szToken[0] = '\0';
1326 0 : nTokenSize = 0;
1327 :
1328 0 : CPLDebug("PDF", "Skipping unknown object %s at line %d",
1329 : osObjectName.c_str(), nLineNumber);
1330 0 : continue;
1331 : }
1332 :
1333 151 : if (!bEmitFeature)
1334 : {
1335 : GDALPDFObject *poXObject =
1336 149 : poResources->GetDictionary()->Get("XObject");
1337 298 : if (poXObject == nullptr ||
1338 149 : poXObject->GetType() != PDFObjectType_Dictionary)
1339 : {
1340 0 : CPLDebug("PDF",
1341 : "Should not happen at line %d: offset %d "
1342 : "in stream",
1343 0 : __LINE__, int(pszContent - pszContentIni));
1344 0 : CPLDebug("PDF",
1345 : "ParseContent(), line %d: return at line "
1346 : "%d of content stream",
1347 : __LINE__, nLineNumber);
1348 0 : return nullptr;
1349 : }
1350 :
1351 : GDALPDFObject *poObject =
1352 298 : poXObject->GetDictionary()->Get(
1353 149 : osObjectName.c_str() + 1);
1354 149 : if (poObject == nullptr)
1355 : {
1356 0 : CPLDebug("PDF",
1357 : "Should not happen at line %d: offset %d "
1358 : "in stream",
1359 0 : __LINE__, int(pszContent - pszContentIni));
1360 0 : CPLDebug("PDF",
1361 : "ParseContent(), line %d: return at line "
1362 : "%d of content stream",
1363 : __LINE__, nLineNumber);
1364 0 : return nullptr;
1365 : }
1366 :
1367 149 : int bParseStream = TRUE;
1368 149 : GDALPDFObject *poSubResources = nullptr;
1369 : /* Check if the object is an image. If so, no need to
1370 : * try to parse */
1371 : /* it. */
1372 149 : if (poObject->GetType() == PDFObjectType_Dictionary)
1373 : {
1374 : GDALPDFObject *poSubtype =
1375 149 : poObject->GetDictionary()->Get("Subtype");
1376 298 : if (poSubtype != nullptr &&
1377 298 : poSubtype->GetType() == PDFObjectType_Name &&
1378 149 : poSubtype->GetName() == "Image")
1379 : {
1380 1 : bParseStream = FALSE;
1381 : }
1382 :
1383 : poSubResources =
1384 149 : poObject->GetDictionary()->Get("Resources");
1385 149 : if (poSubResources && poSubResources->GetType() !=
1386 : PDFObjectType_Dictionary)
1387 : {
1388 0 : poSubResources = nullptr;
1389 : }
1390 : }
1391 :
1392 149 : if (bParseStream)
1393 : {
1394 148 : GDALPDFStream *poStream = poObject->GetStream();
1395 148 : if (!poStream)
1396 : {
1397 0 : CPLDebug("PDF",
1398 : "Should not happen at line %d: offset "
1399 : "%d in stream",
1400 : __LINE__,
1401 0 : int(pszContent - pszContentIni));
1402 0 : CPLDebug("PDF",
1403 : "ParseContent(), line %d: return at "
1404 : "line %d of content stream",
1405 : __LINE__, nLineNumber);
1406 0 : return nullptr;
1407 : }
1408 :
1409 148 : OGRPDFLayer *poCurLayerRec = poCurLayer;
1410 :
1411 148 : if (poObject->GetType() == PDFObjectType_Dictionary)
1412 : {
1413 : auto poOC =
1414 148 : poObject->GetDictionary()->Get("OC");
1415 150 : if (poOC &&
1416 2 : poOC->GetType() ==
1417 150 : PDFObjectType_Dictionary &&
1418 150 : poOC->GetRefNum().toBool())
1419 : {
1420 : const auto oIterNumGenToLayer =
1421 : oMapNumGenToLayer.find(
1422 2 : std::pair(poOC->GetRefNum().toInt(),
1423 4 : poOC->GetRefGen()));
1424 2 : if (oIterNumGenToLayer !=
1425 4 : oMapNumGenToLayer.end())
1426 : {
1427 2 : poCurLayerRec =
1428 2 : oIterNumGenToLayer->second;
1429 : }
1430 : }
1431 : }
1432 :
1433 148 : char *pszStr = poStream->GetBytes();
1434 148 : if (pszStr)
1435 : {
1436 146 : CPLDebug("PDF", "Starting parsing %s",
1437 : osObjectName.c_str());
1438 146 : OGRGeometry *poGeom = ParseContent(
1439 : pszStr, poSubResources, bCollectAllObjects,
1440 : false, false, oMapPropertyToLayer,
1441 : oMapNumGenToLayer, oGS, poCurLayerRec,
1442 : nRecLevel + 1);
1443 146 : CPLDebug("PDF", "End of parsing of %s",
1444 : osObjectName.c_str());
1445 146 : CPLFree(pszStr);
1446 146 : if (poGeom && !bCollectAllObjects)
1447 37 : return poGeom;
1448 109 : delete poGeom;
1449 : }
1450 : }
1451 114 : }
1452 : }
1453 302001 : else if (EQUAL2(szToken, "RG") || EQUAL2(szToken, "rg"))
1454 : {
1455 205 : double *padf = (EQUAL2(szToken, "RG"))
1456 614 : ? &oGS.adfStrokeColor[0]
1457 204 : : &oGS.adfFillColor[0];
1458 409 : if (!UnstackTokens(szToken, 3, aszTokenStack,
1459 : nTokenStackSize, padf))
1460 : {
1461 0 : CPLDebug(
1462 : "PDF",
1463 : "Should not happen at line %d: offset %d in stream",
1464 0 : __LINE__, int(pszContent - pszContentIni));
1465 0 : CPLDebug("PDF",
1466 : "ParseContent(), line %d: return at line %d "
1467 : "of content stream",
1468 : __LINE__, nLineNumber);
1469 0 : return nullptr;
1470 409 : }
1471 : }
1472 301592 : else if (m_oMapOperators.find(szToken) != m_oMapOperators.end())
1473 : {
1474 301592 : int nArgs = m_oMapOperators[szToken];
1475 301592 : if (nArgs < 0)
1476 : {
1477 39142 : while (nTokenStackSize != 0)
1478 : {
1479 : CPLString osTopToken =
1480 29335 : aszTokenStack[--nTokenStackSize];
1481 29335 : if (m_oMapOperators.find(osTopToken) !=
1482 58670 : m_oMapOperators.end())
1483 0 : break;
1484 : }
1485 : }
1486 : else
1487 : {
1488 291785 : if (nArgs > nTokenStackSize)
1489 : {
1490 0 : CPLDebug("PDF", "not enough arguments for %s",
1491 : szToken);
1492 0 : CPLDebug("PDF",
1493 : "ParseContent(), line %d: return at line "
1494 : "%d of content stream",
1495 : __LINE__, nLineNumber);
1496 0 : return nullptr;
1497 : }
1498 291785 : nTokenStackSize -= nArgs;
1499 : }
1500 : }
1501 : else
1502 : {
1503 0 : PUSH(aszTokenStack, szToken, nTokenSize);
1504 : }
1505 :
1506 19589000 : if (bEmitFeature && poCurLayer != nullptr)
1507 : {
1508 : auto poGeom = std::unique_ptr<OGRGeometry>(
1509 640934 : BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart));
1510 320467 : bHasFoundFill = FALSE;
1511 320467 : bHasMultiPart = FALSE;
1512 320467 : if (poGeom)
1513 : {
1514 : auto poFeature = std::make_unique<OGRFeature>(
1515 320466 : poCurLayer->GetLayerDefn());
1516 320466 : if (m_bSetStyle)
1517 : {
1518 : OGRwkbGeometryType eType =
1519 320466 : wkbFlatten(poGeom->getGeometryType());
1520 320466 : if (eType == wkbLineString ||
1521 : eType == wkbMultiLineString)
1522 : {
1523 546152 : poFeature->SetStyleString(CPLSPrintf(
1524 : "PEN(c:#%02X%02X%02X)",
1525 : static_cast<int>(
1526 273076 : oGS.adfStrokeColor[0] * 255 + 0.5),
1527 : static_cast<int>(
1528 273076 : oGS.adfStrokeColor[1] * 255 + 0.5),
1529 : static_cast<int>(
1530 273076 : oGS.adfStrokeColor[2] * 255 + 0.5)));
1531 : }
1532 47390 : else if (eType == wkbPolygon ||
1533 : eType == wkbMultiPolygon)
1534 : {
1535 94736 : poFeature->SetStyleString(CPLSPrintf(
1536 : "PEN(c:#%02X%02X%02X);BRUSH(fc:#%02X%02X%"
1537 : "02X)",
1538 : static_cast<int>(
1539 47368 : oGS.adfStrokeColor[0] * 255 + 0.5),
1540 : static_cast<int>(
1541 47368 : oGS.adfStrokeColor[1] * 255 + 0.5),
1542 : static_cast<int>(
1543 47368 : oGS.adfStrokeColor[2] * 255 + 0.5),
1544 47368 : static_cast<int>(oGS.adfFillColor[0] * 255 +
1545 : 0.5),
1546 47368 : static_cast<int>(oGS.adfFillColor[1] * 255 +
1547 : 0.5),
1548 47368 : static_cast<int>(oGS.adfFillColor[2] * 255 +
1549 47368 : 0.5)));
1550 : }
1551 : }
1552 640932 : poGeom->assignSpatialReference(
1553 320466 : poCurLayer->GetSpatialRef());
1554 320466 : poFeature->SetGeometry(std::move(poGeom));
1555 320466 : CPL_IGNORE_RET_VAL(
1556 320466 : poCurLayer->CreateFeature(std::move(poFeature)));
1557 : }
1558 :
1559 320467 : oCoords.resize(0);
1560 : }
1561 : }
1562 :
1563 19984700 : szToken[0] = '\0';
1564 19984700 : nTokenSize = 0;
1565 : }
1566 : }
1567 :
1568 158 : CPLDebug("PDF", "ParseContent(): reached line %d", nLineNumber);
1569 158 : if (!oGSStack.empty())
1570 0 : CPLDebug("PDF", "GSStack not empty");
1571 :
1572 158 : if (nTokenStackSize != 0)
1573 : {
1574 0 : while (nTokenStackSize != 0)
1575 : {
1576 0 : nTokenStackSize--;
1577 0 : CPLDebug("PDF", "Remaining values in stack : %s",
1578 0 : aszTokenStack[nTokenStackSize]);
1579 : }
1580 0 : return nullptr;
1581 : }
1582 :
1583 158 : if (bCollectAllObjects)
1584 117 : return nullptr;
1585 :
1586 41 : return BuildGeometry(oCoords, bHasFoundFill, bHasMultiPart).release();
1587 : }
1588 :
1589 : /************************************************************************/
1590 : /* BuildGeometry() */
1591 : /************************************************************************/
1592 :
1593 : std::unique_ptr<OGRGeometry>
1594 320508 : PDFDataset::BuildGeometry(std::vector<double> &oCoords, int bHasFoundFill,
1595 : int bHasMultiPart)
1596 : {
1597 320508 : std::unique_ptr<OGRGeometry> poGeom;
1598 :
1599 320508 : if (!oCoords.size())
1600 5 : return nullptr;
1601 :
1602 320503 : if (oCoords.size() == 2)
1603 : {
1604 : double X, Y;
1605 4 : PDFCoordsToSRSCoords(oCoords[0], oCoords[1], X, Y);
1606 4 : poGeom = std::make_unique<OGRPoint>(X, Y);
1607 : }
1608 320499 : else if (!bHasFoundFill)
1609 : {
1610 273104 : std::unique_ptr<OGRLineString> poLS;
1611 273104 : std::unique_ptr<OGRMultiLineString> poMLS;
1612 273104 : if (bHasMultiPart)
1613 : {
1614 15646 : poMLS = std::make_unique<OGRMultiLineString>();
1615 : }
1616 :
1617 7851030 : for (size_t i = 0; i < oCoords.size(); i += 2)
1618 : {
1619 7577930 : if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH)
1620 : {
1621 294252 : if (poMLS && poLS && !poLS->IsEmpty())
1622 21148 : poMLS->addGeometry(std::move(poLS));
1623 294252 : poLS = std::make_unique<OGRLineString>();
1624 : }
1625 7532920 : else if (oCoords[i] == CLOSE_SUBPATH &&
1626 249239 : oCoords[i + 1] == CLOSE_SUBPATH)
1627 : {
1628 740399 : if (poLS && poLS->getNumPoints() >= 2 &&
1629 491160 : !(poLS->getX(0) == poLS->getX(poLS->getNumPoints() - 1) &&
1630 241942 : poLS->getY(0) == poLS->getY(poLS->getNumPoints() - 1)))
1631 : {
1632 7280 : poLS->addPoint(poLS->getX(0), poLS->getY(0));
1633 : }
1634 : }
1635 7034440 : else if (oCoords[i] == FILL_SUBPATH &&
1636 0 : oCoords[i + 1] == FILL_SUBPATH)
1637 : {
1638 : /* Should not happen */
1639 : }
1640 7034440 : else if (poLS)
1641 : {
1642 : double X, Y;
1643 7034440 : PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y);
1644 :
1645 7034440 : poLS->addPoint(X, Y);
1646 : }
1647 : }
1648 :
1649 : // Recognize points as written by GDAL (ogr-sym-2 : circle (not filled))
1650 273231 : if (poLS != nullptr && poLS->getNumPoints() == 1 + BEZIER_STEPS * 4 &&
1651 273231 : (poGeom = PDFGetCircleCenter(poLS.get())) != nullptr)
1652 : {
1653 : // done
1654 : }
1655 :
1656 : // Recognize points as written by GDAL (ogr-sym-4: square (not filled))
1657 819300 : else if (poLS != nullptr &&
1658 358036 : (poLS->getNumPoints() == 4 || poLS->getNumPoints() == 5) &&
1659 358036 : (poGeom = PDFGetSquareCenter(poLS.get())) != nullptr)
1660 : {
1661 : // done
1662 : }
1663 :
1664 : // Recognize points as written by GDAL (ogr-sym-6: triangle (not
1665 : // filled))
1666 819288 : else if (poLS != nullptr &&
1667 282670 : (poLS->getNumPoints() == 3 || poLS->getNumPoints() == 4) &&
1668 282670 : (poGeom = PDFGetTriangleCenter(poLS.get())) != nullptr)
1669 : {
1670 : // done
1671 : }
1672 :
1673 : // Recognize points as written by GDAL (ogr-sym-8: star (not filled))
1674 819276 : else if (poLS != nullptr &&
1675 287205 : (poLS->getNumPoints() == 10 || poLS->getNumPoints() == 11) &&
1676 287205 : (poGeom = PDFGetStarCenter(poLS.get())) != nullptr)
1677 : {
1678 : // done
1679 : }
1680 273088 : else if (poMLS)
1681 : {
1682 15646 : if (poLS && !poLS->IsEmpty())
1683 15646 : poMLS->addGeometry(std::move(poLS));
1684 :
1685 15646 : if (poMLS->getNumGeometries() == 2)
1686 : {
1687 13297 : const OGRLineString *poLS1 = poMLS->getGeometryRef(0);
1688 13297 : const OGRLineString *poLS2 = poMLS->getGeometryRef(1);
1689 :
1690 : // Recognize points as written by GDAL (ogr-sym-0: cross (+) ).
1691 13637 : if (poLS1->getNumPoints() == 2 && poLS2->getNumPoints() == 2 &&
1692 83 : poLS1->getY(0) == poLS1->getY(1) &&
1693 4 : poLS2->getX(0) == poLS2->getX(1) &&
1694 4 : fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) -
1695 4 : fabs(poLS2->getY(0) - poLS2->getY(1))) < EPSILON &&
1696 8 : fabs((poLS1->getX(0) + poLS1->getX(1)) / 2 -
1697 13562 : poLS2->getX(0)) < EPSILON &&
1698 8 : fabs((poLS2->getY(0) + poLS2->getY(1)) / 2 -
1699 4 : poLS1->getY(0)) < EPSILON)
1700 : {
1701 8 : poGeom = std::make_unique<OGRPoint>(poLS2->getX(0),
1702 12 : poLS1->getY(0));
1703 : }
1704 : // Recognize points as written by GDAL (ogr-sym-1: diagcross (X) ).
1705 13293 : else if (poLS1->getNumPoints() == 2 &&
1706 332 : poLS2->getNumPoints() == 2 &&
1707 79 : poLS1->getX(0) == poLS2->getX(0) &&
1708 8 : poLS1->getY(0) == poLS2->getY(1) &&
1709 8 : poLS1->getX(1) == poLS2->getX(1) &&
1710 13554 : poLS1->getY(1) == poLS2->getY(0) &&
1711 4 : fabs(fabs(poLS1->getX(0) - poLS1->getX(1)) -
1712 4 : fabs(poLS1->getY(0) - poLS1->getY(1))) < EPSILON)
1713 : {
1714 4 : poGeom = std::make_unique<OGRPoint>(
1715 4 : (poLS1->getX(0) + poLS1->getX(1)) / 2,
1716 12 : (poLS1->getY(0) + poLS1->getY(1)) / 2);
1717 : }
1718 : }
1719 :
1720 15646 : if (!poGeom)
1721 15638 : poGeom = std::move(poMLS);
1722 : }
1723 257442 : else if (poLS)
1724 : {
1725 257442 : poGeom = std::move(poLS);
1726 : }
1727 : }
1728 : else
1729 : {
1730 47395 : std::unique_ptr<OGRLinearRing> poLS;
1731 94790 : std::vector<std::unique_ptr<OGRGeometry>> apoPolys;
1732 :
1733 5832070 : for (size_t i = 0; i < oCoords.size(); i += 2)
1734 : {
1735 5784690 : if (oCoords[i] == NEW_SUBPATH && oCoords[i + 1] == NEW_SUBPATH)
1736 : {
1737 91063 : if (poLS && poLS->getNumPoints() >= 3)
1738 : {
1739 3 : auto poPoly = std::make_unique<OGRPolygon>();
1740 3 : poPoly->addRing(std::move(poLS));
1741 3 : apoPolys.push_back(std::move(poPoly));
1742 : }
1743 91063 : poLS = std::make_unique<OGRLinearRing>();
1744 : }
1745 5693630 : else if ((oCoords[i] == CLOSE_SUBPATH &&
1746 11296200 : oCoords[i + 1] == CLOSE_SUBPATH) ||
1747 5602580 : (oCoords[i] == FILL_SUBPATH &&
1748 47374 : oCoords[i + 1] == FILL_SUBPATH))
1749 : {
1750 138428 : if (poLS)
1751 : {
1752 91060 : poLS->closeRings();
1753 :
1754 0 : std::unique_ptr<OGRPoint> poCenter;
1755 :
1756 138452 : if (apoPolys.empty() && poLS &&
1757 47392 : poLS->getNumPoints() == 1 + BEZIER_STEPS * 4)
1758 : {
1759 : // Recognize points as written by GDAL (ogr-sym-3 :
1760 : // circle (filled))
1761 2356 : poCenter = PDFGetCircleCenter(poLS.get());
1762 : }
1763 :
1764 138442 : if (apoPolys.empty() && poCenter == nullptr && poLS &&
1765 47382 : poLS->getNumPoints() == 5)
1766 : {
1767 : // Recognize points as written by GDAL (ogr-sym-5:
1768 : // square (filled))
1769 8502 : poCenter = PDFGetSquareCenter(poLS.get());
1770 :
1771 : /* ESRI points */
1772 22428 : if (poCenter == nullptr && oCoords.size() == 14 &&
1773 5428 : poLS->getY(0) == poLS->getY(1) &&
1774 0 : poLS->getX(1) == poLS->getX(2) &&
1775 17000 : poLS->getY(2) == poLS->getY(3) &&
1776 0 : poLS->getX(3) == poLS->getX(0))
1777 : {
1778 0 : poCenter = std::make_unique<OGRPoint>(
1779 0 : (poLS->getX(0) + poLS->getX(1)) / 2,
1780 0 : (poLS->getY(0) + poLS->getY(2)) / 2);
1781 : }
1782 : }
1783 : // Recognize points as written by GDAL (ogr-sym-7: triangle
1784 : // (filled))
1785 121448 : else if (apoPolys.empty() && poLS &&
1786 38890 : poLS->getNumPoints() == 4)
1787 : {
1788 5933 : poCenter = PDFGetTriangleCenter(poLS.get());
1789 : }
1790 : // Recognize points as written by GDAL (ogr-sym-9: star
1791 : // (filled))
1792 109582 : else if (apoPolys.empty() && poLS &&
1793 32957 : poLS->getNumPoints() == 11)
1794 : {
1795 424 : poCenter = PDFGetStarCenter(poLS.get());
1796 : }
1797 :
1798 91060 : if (poCenter)
1799 : {
1800 22 : poGeom = std::move(poCenter);
1801 22 : break;
1802 : }
1803 :
1804 91038 : if (poLS->getNumPoints() >= 3)
1805 : {
1806 91034 : auto poPoly = std::make_unique<OGRPolygon>();
1807 91034 : poPoly->addRing(std::move(poLS));
1808 91034 : apoPolys.push_back(std::move(poPoly));
1809 : }
1810 91038 : poLS.reset();
1811 : }
1812 : }
1813 : else
1814 : {
1815 5555200 : if (poLS)
1816 : {
1817 : double X, Y;
1818 5555200 : PDFCoordsToSRSCoords(oCoords[i], oCoords[i + 1], X, Y);
1819 :
1820 5555200 : poLS->addPoint(X, Y);
1821 : }
1822 : }
1823 : }
1824 :
1825 47395 : if (apoPolys.size() == 2 &&
1826 60618 : apoPolys[0]->toPolygon()->getNumInteriorRings() == 0 &&
1827 13223 : apoPolys[1]->toPolygon()->getNumInteriorRings() == 0)
1828 : {
1829 13223 : const auto poRing0 = apoPolys[0]->toPolygon()->getExteriorRing();
1830 13223 : const auto poRing1 = apoPolys[1]->toPolygon()->getExteriorRing();
1831 13223 : if (poRing0->Equals(poRing1))
1832 : {
1833 : /* Just keep one ring if they are identical */
1834 0 : apoPolys.resize(1);
1835 : }
1836 : }
1837 47395 : if (!apoPolys.empty())
1838 : {
1839 47373 : poGeom = OGRGeometryFactory::organizePolygons(apoPolys);
1840 : }
1841 : }
1842 :
1843 320503 : return poGeom;
1844 : }
1845 :
1846 : /************************************************************************/
1847 : /* ExploreContents() */
1848 : /************************************************************************/
1849 :
1850 5 : void PDFDataset::ExploreContents(GDALPDFObject *poObj,
1851 : GDALPDFObject *poResources, int nDepth,
1852 : int &nVisited, bool &bStop)
1853 : {
1854 5 : std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer;
1855 5 : if (nDepth == 10 || nVisited == 1000)
1856 : {
1857 0 : CPLError(CE_Failure, CPLE_AppDefined,
1858 : "ExploreContents(): too deep exploration or too many items");
1859 0 : bStop = true;
1860 0 : return;
1861 : }
1862 5 : if (bStop)
1863 0 : return;
1864 :
1865 5 : if (poObj->GetType() == PDFObjectType_Array)
1866 : {
1867 0 : GDALPDFArray *poArray = poObj->GetArray();
1868 0 : for (int i = 0; i < poArray->GetLength(); i++)
1869 : {
1870 0 : GDALPDFObject *poSubObj = poArray->Get(i);
1871 0 : if (poSubObj)
1872 : {
1873 0 : nVisited++;
1874 0 : ExploreContents(poSubObj, poResources, nDepth + 1, nVisited,
1875 : bStop);
1876 0 : if (bStop)
1877 0 : return;
1878 : }
1879 : }
1880 : }
1881 :
1882 5 : if (poObj->GetType() != PDFObjectType_Dictionary)
1883 0 : return;
1884 :
1885 5 : GDALPDFStream *poStream = poObj->GetStream();
1886 5 : if (!poStream)
1887 0 : return;
1888 :
1889 5 : char *pszStr = poStream->GetBytes();
1890 5 : if (!pszStr)
1891 0 : return;
1892 :
1893 5 : const char *pszMCID = pszStr;
1894 45 : while ((pszMCID = strstr(pszMCID, "/MCID")) != nullptr)
1895 : {
1896 40 : const char *pszBDC = strstr(pszMCID, "BDC");
1897 40 : if (pszBDC)
1898 : {
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 40 : const char *pszStartParsing = pszBDC;
1904 40 : const char *pszAfterBDC = pszBDC + 3;
1905 40 : int bMatchQ = FALSE;
1906 80 : while (pszAfterBDC[0] == ' ' || pszAfterBDC[0] == '\r' ||
1907 80 : pszAfterBDC[0] == '\n')
1908 40 : pszAfterBDC++;
1909 40 : if (STARTS_WITH(pszAfterBDC, "0 0 m"))
1910 : {
1911 0 : const char *pszLastq = pszBDC;
1912 0 : while (pszLastq > pszStr && *pszLastq != 'q')
1913 0 : pszLastq--;
1914 :
1915 0 : 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 : {
1921 0 : pszStartParsing = pszLastq;
1922 0 : bMatchQ = TRUE;
1923 : }
1924 : }
1925 :
1926 40 : const int nMCID = atoi(pszMCID + 6);
1927 40 : if (GetGeometryFromMCID(nMCID) == nullptr)
1928 : {
1929 : auto poGeom = std::unique_ptr<OGRGeometry>(ParseContent(
1930 : pszStartParsing, poResources, false, !bMatchQ, bMatchQ,
1931 78 : oMapPropertyToLayer, {}, GraphicState(), nullptr, 0));
1932 39 : if (poGeom != nullptr)
1933 : {
1934 : /* Save geometry in map */
1935 37 : m_oMapMCID[nMCID] = std::move(poGeom);
1936 : }
1937 : }
1938 : }
1939 40 : pszMCID += 5;
1940 : }
1941 5 : CPLFree(pszStr);
1942 : }
1943 :
1944 : /************************************************************************/
1945 : /* ExploreContentsNonStructured() */
1946 : /************************************************************************/
1947 :
1948 10 : 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 : {
1954 10 : if (poContents->GetType() == PDFObjectType_Array)
1955 : {
1956 1 : GDALPDFArray *poArray = poContents->GetArray();
1957 1 : char *pszConcatStr = nullptr;
1958 1 : size_t nConcatLen = 0;
1959 2 : for (int i = 0; i < poArray->GetLength(); i++)
1960 : {
1961 1 : GDALPDFObject *poObj = poArray->Get(i);
1962 2 : if (poObj == nullptr ||
1963 1 : poObj->GetType() != PDFObjectType_Dictionary)
1964 0 : break;
1965 1 : GDALPDFStream *poStream = poObj->GetStream();
1966 1 : if (!poStream)
1967 0 : break;
1968 1 : char *pszStr = poStream->GetBytes();
1969 1 : if (!pszStr)
1970 0 : break;
1971 1 : const size_t nLen = strlen(pszStr);
1972 : char *pszConcatStrNew = static_cast<char *>(
1973 1 : CPLRealloc(pszConcatStr, nConcatLen + nLen + 1));
1974 1 : if (pszConcatStrNew == nullptr)
1975 : {
1976 0 : CPLFree(pszStr);
1977 0 : break;
1978 : }
1979 1 : pszConcatStr = pszConcatStrNew;
1980 1 : memcpy(pszConcatStr + nConcatLen, pszStr, nLen + 1);
1981 1 : nConcatLen += nLen;
1982 1 : CPLFree(pszStr);
1983 : }
1984 1 : if (pszConcatStr)
1985 1 : ParseContent(pszConcatStr, poResources, poResources != nullptr,
1986 : false, false, oMapPropertyToLayer, oMapNumGenToLayer,
1987 2 : GraphicState(), poSingleLayer, 0);
1988 1 : CPLFree(pszConcatStr);
1989 1 : return;
1990 : }
1991 :
1992 9 : if (poContents->GetType() != PDFObjectType_Dictionary)
1993 0 : return;
1994 :
1995 9 : GDALPDFStream *poStream = poContents->GetStream();
1996 9 : if (!poStream)
1997 0 : return;
1998 :
1999 9 : char *pszStr = poStream->GetBytes();
2000 9 : if (!pszStr)
2001 0 : return;
2002 9 : ParseContent(pszStr, poResources, poResources != nullptr, false, false,
2003 9 : oMapPropertyToLayer, oMapNumGenToLayer, GraphicState(),
2004 : poSingleLayer, 0);
2005 9 : CPLFree(pszStr);
2006 : }
2007 :
2008 : /************************************************************************/
2009 : /* ExploreResourceProperty() */
2010 : /************************************************************************/
2011 :
2012 154 : 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 : {
2017 154 : if (nRecLevel == 2)
2018 0 : return;
2019 :
2020 154 : if (osType == "OCG" && poObj->GetRefNum().toBool())
2021 : {
2022 : const auto oIterNumGenToLayer = oMapNumGenToLayer.find(
2023 153 : std::pair(poObj->GetRefNum().toInt(), poObj->GetRefGen()));
2024 153 : if (oIterNumGenToLayer != oMapNumGenToLayer.end())
2025 : {
2026 153 : auto poLayer = oIterNumGenToLayer->second;
2027 : #ifdef DEBUG_VERBOSE
2028 : CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey,
2029 : poLayer->GetName());
2030 : #endif
2031 153 : oMapPropertyToLayer[pszKey] = poLayer;
2032 : }
2033 : else
2034 : {
2035 0 : CPLDebug("PDF",
2036 : "Resource.Properties[%s] referencing "
2037 : "OGC %d not tied with a layer",
2038 0 : pszKey, poObj->GetRefNum().toInt());
2039 : }
2040 : }
2041 1 : else if (osType == "OCMD")
2042 : {
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 1 : auto poOCGs = poObj->GetDictionary()->Get("OCGs");
2070 1 : if (poOCGs && poOCGs->GetType() == PDFObjectType_Array)
2071 : {
2072 1 : auto poOCGsArray = poOCGs->GetArray();
2073 1 : const int nLength = poOCGsArray->GetLength();
2074 1 : size_t nMaxNameLength = 0;
2075 1 : OGRPDFLayer *poCandidateLayer = nullptr;
2076 2 : std::vector<std::string> aosLayerNames;
2077 3 : for (int i = 0; i < nLength; ++i)
2078 : {
2079 2 : auto poOCG = poOCGsArray->Get(i);
2080 2 : if (poOCG && poOCG->GetType() == PDFObjectType_Dictionary)
2081 : {
2082 2 : auto poP = poOCG->GetDictionary()->Get("P");
2083 2 : if (poP && poP->GetType() == PDFObjectType_Name)
2084 : {
2085 : // Visibility Policy
2086 0 : const auto &osP = poP->GetName();
2087 0 : if (osP != "AllOn" && osP != "AnyOn")
2088 : {
2089 0 : CPLDebug("PDF",
2090 : "Resource.Properties[%s] "
2091 : "has unhandled visibility policy %s",
2092 : pszKey, osP.c_str());
2093 : }
2094 : }
2095 2 : auto poOCGType = poOCG->GetDictionary()->Get("Type");
2096 2 : if (poOCGType && poOCGType->GetType() == PDFObjectType_Name)
2097 : {
2098 2 : const std::string &osOCGType = poOCGType->GetName();
2099 2 : if (osOCGType == "OCG" && poOCG->GetRefNum().toBool())
2100 : {
2101 : const auto oIterNumGenToLayer =
2102 : oMapNumGenToLayer.find(
2103 2 : std::pair(poOCG->GetRefNum().toInt(),
2104 4 : poOCG->GetRefGen()));
2105 2 : if (oIterNumGenToLayer != oMapNumGenToLayer.end())
2106 : {
2107 2 : auto poLayer = oIterNumGenToLayer->second;
2108 2 : aosLayerNames.emplace_back(poLayer->GetName());
2109 2 : if (strlen(poLayer->GetName()) > nMaxNameLength)
2110 : {
2111 2 : nMaxNameLength = strlen(poLayer->GetName());
2112 2 : poCandidateLayer = poLayer;
2113 : }
2114 : }
2115 : else
2116 : {
2117 0 : CPLDebug("PDF",
2118 : "Resource.Properties[%s][%d] "
2119 : "referencing OGC %d not tied with "
2120 : "a layer",
2121 0 : pszKey, i, poOCG->GetRefNum().toInt());
2122 : }
2123 : }
2124 : else
2125 : {
2126 0 : CPLDebug(
2127 : "PDF",
2128 : "Resource.Properties[%s][%d] has unhandled "
2129 : "Type member: %s",
2130 : pszKey, i, osOCGType.c_str());
2131 : }
2132 : }
2133 : }
2134 : }
2135 :
2136 1 : if (!aosLayerNames.empty())
2137 : {
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 1 : std::sort(aosLayerNames.begin(), aosLayerNames.end());
2144 1 : bool bOK = true;
2145 2 : for (size_t i = 1; i < aosLayerNames.size(); ++i)
2146 : {
2147 1 : if (aosLayerNames[i].find(aosLayerNames[i - 1]) != 0)
2148 : {
2149 0 : bOK = false;
2150 0 : break;
2151 : }
2152 : }
2153 1 : if (bOK)
2154 : {
2155 1 : CPLAssert(poCandidateLayer);
2156 : #ifdef DEBUG_VERBOSE
2157 : CPLDebug("PDF", "Associating OCG %s to layer %s", pszKey,
2158 : poCandidateLayer->GetName());
2159 : #endif
2160 1 : oMapPropertyToLayer[pszKey] = poCandidateLayer;
2161 : }
2162 : else
2163 : {
2164 0 : CPLDebug("PDF",
2165 : "Resource.Properties[%s] "
2166 : "contains a OCMD that cannot "
2167 : "be mapped to a single layer",
2168 : pszKey);
2169 : }
2170 : }
2171 : else
2172 : {
2173 0 : CPLDebug("PDF",
2174 : "Resource.Properties[%s] contains "
2175 : "a OCMD without OCGs",
2176 : pszKey);
2177 : }
2178 : }
2179 0 : else if (poOCGs && poOCGs->GetType() == PDFObjectType_Dictionary)
2180 : {
2181 0 : auto poOGGsType = poOCGs->GetDictionary()->Get("Type");
2182 0 : if (poOGGsType && poOGGsType->GetType() == PDFObjectType_Name)
2183 : {
2184 0 : ExploreResourceProperty(pszKey, poOCGs, poOGGsType->GetName(),
2185 : oMapNumGenToLayer, oMapPropertyToLayer,
2186 : nRecLevel + 1);
2187 : }
2188 : else
2189 : {
2190 0 : CPLDebug("PDF",
2191 : "Resource.Properties[%s] contains a OGCs member with "
2192 : "no Type member",
2193 : pszKey);
2194 : }
2195 : }
2196 0 : else if (poOCGs)
2197 : {
2198 0 : CPLDebug("PDF",
2199 : "Resource.Properties[%s] contains a OCMD "
2200 : "with a OGCs member of unhandled type: %s",
2201 0 : pszKey, poOCGs->GetTypeName());
2202 : }
2203 : else
2204 : {
2205 : // Could have a VE (visibility expression)
2206 : // expression instead, but we don't handle that
2207 0 : CPLDebug("PDF",
2208 : "Resource.Properties[%s] contains a "
2209 : "OCMD with a missing OGC (perhaps has a VE?)",
2210 : pszKey);
2211 : }
2212 : }
2213 : else
2214 : {
2215 0 : CPLDebug("PDF",
2216 : "Resource.Properties[%s] has unhandled "
2217 : "Type member: %s",
2218 : pszKey, osType.c_str());
2219 : }
2220 : }
2221 :
2222 : /************************************************************************/
2223 : /* ExploreContentsNonStructured() */
2224 : /************************************************************************/
2225 :
2226 17 : void PDFDataset::ExploreContentsNonStructured(GDALPDFObject *poContents,
2227 : GDALPDFObject *poResources)
2228 : {
2229 17 : std::map<CPLString, OGRPDFLayer *> oMapPropertyToLayer;
2230 17 : std::map<std::pair<int, int>, OGRPDFLayer *> oMapNumGenToLayer;
2231 :
2232 1256 : const auto BuildMapNumGenToLayer = [this, &oMapNumGenToLayer]()
2233 : {
2234 228 : for (const auto &oLayerWithref : m_aoLayerWithRef)
2235 : {
2236 : CPLString osSanitizedName(
2237 220 : PDFSanitizeLayerName(oLayerWithref.osName));
2238 :
2239 202 : OGRPDFLayer *poPDFLayer = dynamic_cast<OGRPDFLayer *>(
2240 220 : GetLayerByName(osSanitizedName.c_str()));
2241 220 : if (!poPDFLayer)
2242 : {
2243 202 : auto poSRSOri = GetSpatialRef();
2244 : OGRSpatialReference *poSRS =
2245 202 : poSRSOri ? poSRSOri->Clone() : nullptr;
2246 : auto poPDFLayerUniquePtr = std::make_unique<OGRPDFLayer>(
2247 404 : this, osSanitizedName.c_str(), poSRS, wkbUnknown);
2248 202 : if (poSRS)
2249 202 : poSRS->Release();
2250 :
2251 202 : m_apoLayers.emplace_back(std::move(poPDFLayerUniquePtr));
2252 202 : poPDFLayer = m_apoLayers.back().get();
2253 : }
2254 :
2255 220 : oMapNumGenToLayer[std::pair(oLayerWithref.nOCGNum.toInt(),
2256 440 : oLayerWithref.nOCGGen)] = poPDFLayer;
2257 : }
2258 8 : };
2259 :
2260 34 : if (poResources != nullptr &&
2261 17 : poResources->GetType() == PDFObjectType_Dictionary)
2262 : {
2263 17 : auto poResourcesDict = poResources->GetDictionary();
2264 17 : GDALPDFObject *poTopProperties = poResourcesDict->Get("Properties");
2265 24 : if (poTopProperties != nullptr &&
2266 7 : poTopProperties->GetType() == PDFObjectType_Dictionary)
2267 : {
2268 7 : BuildMapNumGenToLayer();
2269 :
2270 34 : for (const auto &[osKey, poObj] :
2271 41 : poTopProperties->GetDictionary()->GetValues())
2272 : {
2273 17 : const char *pszKey = osKey.c_str();
2274 17 : if (poObj->GetType() == PDFObjectType_Dictionary)
2275 : {
2276 17 : auto poType = poObj->GetDictionary()->Get("Type");
2277 17 : if (poType && poType->GetType() == PDFObjectType_Name)
2278 : {
2279 17 : const auto &osType = poType->GetName();
2280 17 : ExploreResourceProperty(pszKey, poObj, osType,
2281 : oMapNumGenToLayer,
2282 : oMapPropertyToLayer, 0);
2283 : }
2284 : else
2285 : {
2286 0 : CPLDebug("PDF",
2287 : "Resource.Properties[%s] has no Type member",
2288 : pszKey);
2289 : }
2290 : }
2291 : }
2292 : }
2293 : else
2294 : {
2295 : // Code path taken for datasets mentioned at https://github.com/OSGeo/gdal/issues/9870
2296 : // generated by ArcGIS 12.9
2297 10 : const auto poXObject = poResourcesDict->Get("XObject");
2298 10 : if (poXObject && poXObject->GetType() == PDFObjectType_Dictionary)
2299 : {
2300 7 : for (const auto &oNameObjectPair :
2301 22 : poXObject->GetDictionary()->GetValues())
2302 : {
2303 : const auto poProperties =
2304 8 : oNameObjectPair.second->LookupObject(
2305 : "Resources.Properties");
2306 9 : if (poProperties &&
2307 1 : poProperties->GetType() == PDFObjectType_Dictionary)
2308 : {
2309 1 : BuildMapNumGenToLayer();
2310 :
2311 : const auto &oMap =
2312 1 : poProperties->GetDictionary()->GetValues();
2313 138 : for (const auto &[osKey, poObj] : oMap)
2314 : {
2315 137 : const char *pszKey = osKey.c_str();
2316 137 : if (poObj->GetType() == PDFObjectType_Dictionary)
2317 : {
2318 : GDALPDFObject *poType =
2319 137 : poObj->GetDictionary()->Get("Type");
2320 274 : if (poType &&
2321 137 : poType->GetType() == PDFObjectType_Name)
2322 : {
2323 137 : const auto &osType = poType->GetName();
2324 137 : ExploreResourceProperty(
2325 : pszKey, poObj, osType,
2326 : oMapNumGenToLayer, oMapPropertyToLayer,
2327 : 0);
2328 : }
2329 : }
2330 : }
2331 :
2332 1 : break;
2333 : }
2334 : }
2335 : }
2336 : }
2337 : }
2338 :
2339 17 : OGRPDFLayer *poSingleLayer = nullptr;
2340 17 : if (m_apoLayers.empty())
2341 : {
2342 : const char *pszReadNonStructured =
2343 9 : CPLGetConfigOption("OGR_PDF_READ_NON_STRUCTURED", nullptr);
2344 9 : if (pszReadNonStructured && CPLTestBool(pszReadNonStructured))
2345 : {
2346 : auto poLayer = std::make_unique<OGRPDFLayer>(this, "content",
2347 4 : nullptr, wkbUnknown);
2348 2 : m_apoLayers.emplace_back(std::move(poLayer));
2349 2 : poSingleLayer = m_apoLayers.back().get();
2350 : }
2351 : else
2352 : {
2353 7 : if (!pszReadNonStructured)
2354 : {
2355 7 : CPLDebug(
2356 : "PDF",
2357 : "No structured content nor PDF layers detected, hence "
2358 : "vector content detection is disabled. You may force "
2359 : "vector content detection by setting the "
2360 : "OGR_PDF_READ_NON_STRUCTURED configuration option to YES");
2361 : }
2362 7 : return;
2363 : }
2364 : }
2365 :
2366 10 : ExploreContentsNonStructuredInternal(poContents, poResources,
2367 : oMapPropertyToLayer, oMapNumGenToLayer,
2368 : poSingleLayer);
2369 :
2370 : /* Remove empty layers */
2371 214 : for (auto oIter = m_apoLayers.begin(); oIter != m_apoLayers.end();
2372 : /* do nothing */)
2373 : {
2374 204 : if ((*oIter)->GetFeatureCount(false) == 0)
2375 : {
2376 123 : oIter = m_apoLayers.erase(oIter);
2377 : }
2378 : else
2379 : {
2380 81 : ++oIter;
2381 : }
2382 : }
2383 : }
2384 :
2385 : #endif /* HAVE_PDF_READ_SUPPORT */
|