1 /******************************************************************************
2 * $Id: pdfcreatefromcomposition.cpp 12552f346849e6fc3e97caa8c2e6b0b7823a7a46 2019-08-12 23:21:30 +0200 Even Rouault $
3 *
4 * Project: PDF driver
5 * Purpose: GDALDataset driver for PDF dataset.
6 * Author: Even Rouault, <even dot rouault at spatialys dot com>
7 *
8 ******************************************************************************
9 * Copyright (c) 2019, Even Rouault <even dot rouault at spatialys dot com>
10 *
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included
19 * in all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 ****************************************************************************/
29
30 #include "gdal_pdf.h"
31 #include "pdfcreatecopy.h"
32
33 #include <cmath>
34 #include <cstdlib>
35
36 #include "pdfcreatefromcomposition.h"
37 #include "cpl_conv.h"
38 #include "cpl_minixml.h"
39 #include "cpl_vsi_virtual.h"
40 #include "ogr_geometry.h"
41
42 /************************************************************************/
43 /* GDALPDFComposerWriter() */
44 /************************************************************************/
45
GDALPDFComposerWriter(VSILFILE * fp)46 GDALPDFComposerWriter::GDALPDFComposerWriter(VSILFILE* fp):
47 GDALPDFBaseWriter(fp)
48 {
49 StartNewDoc();
50 }
51
52 /************************************************************************/
53 /* ~GDALPDFComposerWriter() */
54 /************************************************************************/
55
~GDALPDFComposerWriter()56 GDALPDFComposerWriter::~GDALPDFComposerWriter()
57 {
58 Close();
59 }
60
61 /************************************************************************/
62 /* Close() */
63 /************************************************************************/
64
Close()65 void GDALPDFComposerWriter::Close()
66 {
67 if (m_fp)
68 {
69 CPLAssert(!m_bInWriteObj);
70 if (m_nPageResourceId.toBool())
71 {
72 WritePages();
73 WriteXRefTableAndTrailer(false, 0);
74 }
75 }
76 GDALPDFBaseWriter::Close();
77 }
78
79 /************************************************************************/
80 /* CreateOCGOrder() */
81 /************************************************************************/
82
CreateOCGOrder(const TreeOfOCG * parent)83 GDALPDFArrayRW* GDALPDFComposerWriter::CreateOCGOrder(const TreeOfOCG* parent)
84 {
85 auto poArrayOrder = new GDALPDFArrayRW();
86 for( const auto& child: parent->m_children )
87 {
88 poArrayOrder->Add(child->m_nNum, 0);
89 if( !child->m_children.empty() )
90 {
91 poArrayOrder->Add(CreateOCGOrder(child.get()));
92 }
93 }
94 return poArrayOrder;
95 }
96
97 /************************************************************************/
98 /* CollectOffOCG() */
99 /************************************************************************/
100
CollectOffOCG(std::vector<GDALPDFObjectNum> & ar,const TreeOfOCG * parent)101 void GDALPDFComposerWriter::CollectOffOCG(std::vector<GDALPDFObjectNum>& ar,
102 const TreeOfOCG* parent)
103 {
104 if( !parent->m_bInitiallyVisible )
105 ar.push_back(parent->m_nNum);
106 for( const auto& child: parent->m_children )
107 {
108 CollectOffOCG(ar, child.get());
109 }
110 }
111
112 /************************************************************************/
113 /* WritePages() */
114 /************************************************************************/
115
WritePages()116 void GDALPDFComposerWriter::WritePages()
117 {
118 StartObj(m_nPageResourceId);
119 {
120 GDALPDFDictionaryRW oDict;
121 GDALPDFArrayRW* poKids = new GDALPDFArrayRW();
122 oDict.Add("Type", GDALPDFObjectRW::CreateName("Pages"))
123 .Add("Count", (int)m_asPageId.size())
124 .Add("Kids", poKids);
125
126 for(size_t i=0;i<m_asPageId.size();i++)
127 poKids->Add(m_asPageId[i], 0);
128
129 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
130 }
131 EndObj();
132
133 if (m_nStructTreeRootId.toBool())
134 {
135 auto nParentTreeId = AllocNewObject();
136 StartObj(nParentTreeId);
137 VSIFPrintfL(m_fp, "<< /Nums [ ");
138 for( size_t i = 0; i < m_anParentElements.size(); i++ )
139 {
140 VSIFPrintfL(m_fp, "%d %d 0 R ",
141 static_cast<int>(i),
142 m_anParentElements[i].toInt());
143 }
144 VSIFPrintfL(m_fp, " ] >> \n");
145 EndObj();
146
147 StartObj(m_nStructTreeRootId);
148 VSIFPrintfL(m_fp,
149 "<< "
150 "/Type /StructTreeRoot "
151 "/ParentTree %d 0 R "
152 "/K [ ", nParentTreeId.toInt());
153 for( const auto& num: m_anFeatureLayerId )
154 {
155 VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
156 }
157 VSIFPrintfL(m_fp,"] >>\n");
158 EndObj();
159 }
160
161 StartObj(m_nCatalogId);
162 {
163 GDALPDFDictionaryRW oDict;
164 oDict.Add("Type", GDALPDFObjectRW::CreateName("Catalog"))
165 .Add("Pages", m_nPageResourceId, 0);
166 if (m_nOutlinesId.toBool())
167 oDict.Add("Outlines", m_nOutlinesId, 0);
168 if (m_nXMPId.toBool())
169 oDict.Add("Metadata", m_nXMPId, 0);
170 if (!m_asOCGs.empty() )
171 {
172 GDALPDFDictionaryRW* poDictOCProperties = new GDALPDFDictionaryRW();
173 oDict.Add("OCProperties", poDictOCProperties);
174
175 GDALPDFDictionaryRW* poDictD = new GDALPDFDictionaryRW();
176 poDictOCProperties->Add("D", poDictD);
177
178 if( m_bDisplayLayersOnlyOnVisiblePages )
179 {
180 poDictD->Add("ListMode",
181 GDALPDFObjectRW::CreateName("VisiblePages"));
182 }
183
184 /* Build "Order" array of D dict */
185 GDALPDFArrayRW* poArrayOrder = CreateOCGOrder(&m_oTreeOfOGC);
186 poDictD->Add("Order", poArrayOrder);
187
188 /* Build "OFF" array of D dict */
189 std::vector<GDALPDFObjectNum> offOCGs;
190 CollectOffOCG(offOCGs, &m_oTreeOfOGC);
191 if( !offOCGs.empty() )
192 {
193 GDALPDFArrayRW* poArrayOFF = new GDALPDFArrayRW();
194 for( const auto& num: offOCGs )
195 {
196 poArrayOFF->Add(num, 0);
197 }
198
199 poDictD->Add("OFF", poArrayOFF);
200 }
201
202 /* Build "RBGroups" array of D dict */
203 if( !m_oMapExclusiveOCGIdToOCGs.empty() )
204 {
205 GDALPDFArrayRW* poArrayRBGroups = new GDALPDFArrayRW();
206 for( const auto& group: m_oMapExclusiveOCGIdToOCGs )
207 {
208 GDALPDFArrayRW* poGroup = new GDALPDFArrayRW();
209 for( const auto& num: group.second )
210 {
211 poGroup->Add(num, 0);
212 }
213 poArrayRBGroups->Add(poGroup);
214 }
215
216 poDictD->Add("RBGroups", poArrayRBGroups);
217 }
218
219
220 GDALPDFArrayRW* poArrayOGCs = new GDALPDFArrayRW();
221 for(const auto& ocg: m_asOCGs )
222 poArrayOGCs->Add(ocg.nId, 0);
223 poDictOCProperties->Add("OCGs", poArrayOGCs);
224 }
225
226 if (m_nStructTreeRootId.toBool())
227 {
228 GDALPDFDictionaryRW* poDictMarkInfo = new GDALPDFDictionaryRW();
229 oDict.Add("MarkInfo", poDictMarkInfo);
230 poDictMarkInfo->Add("UserProperties", GDALPDFObjectRW::CreateBool(TRUE));
231
232 oDict.Add("StructTreeRoot", m_nStructTreeRootId, 0);
233 }
234
235 if (m_nNamesId.toBool())
236 oDict.Add("Names", m_nNamesId, 0);
237
238 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
239 }
240 EndObj();
241 }
242
243 /************************************************************************/
244 /* CreateLayerTree() */
245 /************************************************************************/
246
CreateLayerTree(const CPLXMLNode * psNode,const GDALPDFObjectNum & nParentId,TreeOfOCG * parent)247 bool GDALPDFComposerWriter::CreateLayerTree(const CPLXMLNode* psNode,
248 const GDALPDFObjectNum& nParentId,
249 TreeOfOCG* parent)
250 {
251 for(const auto* psIter = psNode->psChild; psIter; psIter = psIter->psNext)
252 {
253 if( psIter->eType == CXT_Element &&
254 strcmp(psIter->pszValue, "Layer") == 0 )
255 {
256 const char* pszId = CPLGetXMLValue(psIter, "id", nullptr);
257 if( !pszId )
258 {
259 CPLError(CE_Failure, CPLE_AppDefined,
260 "Missing id attribute in Layer");
261 return false;
262 }
263 const char* pszName = CPLGetXMLValue(psIter, "name", nullptr);
264 if( !pszName )
265 {
266 CPLError(CE_Failure, CPLE_AppDefined,
267 "Missing name attribute in Layer");
268 return false;
269 }
270 if( m_oMapLayerIdToOCG.find(pszId) != m_oMapLayerIdToOCG.end() )
271 {
272 CPLError(CE_Failure, CPLE_AppDefined,
273 "Layer.id = %s is not unique", pszId);
274 return false;
275 }
276
277 const bool bInitiallyVisible = CPLTestBool(
278 CPLGetXMLValue(psIter, "initiallyVisible", "true"));
279
280 const char* pszMutuallyExclusiveGroupId = CPLGetXMLValue(psIter,
281 "mutuallyExclusiveGroupId", nullptr);
282
283 auto nThisObjId = WriteOCG( pszName, nParentId );
284 m_oMapLayerIdToOCG[pszId] = nThisObjId;
285
286 std::unique_ptr<TreeOfOCG> newTreeOfOCG(new TreeOfOCG());
287 newTreeOfOCG->m_nNum = nThisObjId;
288 newTreeOfOCG->m_bInitiallyVisible = bInitiallyVisible;
289 parent->m_children.emplace_back(std::move(newTreeOfOCG));
290
291 if( pszMutuallyExclusiveGroupId )
292 {
293 m_oMapExclusiveOCGIdToOCGs[pszMutuallyExclusiveGroupId].
294 push_back(nThisObjId);
295 }
296
297 if( !CreateLayerTree(psIter, nThisObjId,
298 parent->m_children.back().get()) )
299 {
300 return false;
301 }
302 }
303 }
304 return true;
305 }
306
307
308 /************************************************************************/
309 /* ParseActions() */
310 /************************************************************************/
311
ParseActions(const CPLXMLNode * psNode,std::vector<std::unique_ptr<Action>> & actions)312 bool GDALPDFComposerWriter::ParseActions(const CPLXMLNode* psNode,
313 std::vector<std::unique_ptr<Action>>& actions)
314 {
315 std::set<GDALPDFObjectNum> anONLayers{};
316 std::set<GDALPDFObjectNum> anOFFLayers{};
317 for(const auto* psIter = psNode->psChild; psIter; psIter = psIter->psNext)
318 {
319 if( psIter->eType == CXT_Element &&
320 strcmp(psIter->pszValue, "GotoPageAction") == 0 )
321 {
322 std::unique_ptr<GotoPageAction> poAction(new GotoPageAction());
323 const char* pszPageId = CPLGetXMLValue(psIter, "pageId", nullptr);
324 if( !pszPageId )
325 {
326 CPLError(CE_Failure, CPLE_AppDefined,
327 "Missing pageId attribute in GotoPageAction");
328 return false;
329 }
330
331 auto oIter = m_oMapPageIdToObjectNum.find(pszPageId);
332 if( oIter == m_oMapPageIdToObjectNum.end() )
333 {
334 CPLError(CE_Failure, CPLE_AppDefined,
335 "GotoPageAction.pageId = %s not pointing to a Page.id",
336 pszPageId);
337 return false;
338 }
339 poAction->m_nPageDestId = oIter->second;
340 poAction->m_dfX1 = CPLAtof(CPLGetXMLValue(psIter, "x1", "0"));
341 poAction->m_dfX2 = CPLAtof(CPLGetXMLValue(psIter, "y1", "0"));
342 poAction->m_dfY1 = CPLAtof(CPLGetXMLValue(psIter, "x2", "0"));
343 poAction->m_dfY2 = CPLAtof(CPLGetXMLValue(psIter, "y2", "0"));
344 actions.push_back(std::move(poAction));
345 }
346 else if( psIter->eType == CXT_Element &&
347 strcmp(psIter->pszValue, "SetAllLayersStateAction") == 0 )
348 {
349 if( CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")) )
350 {
351 for( const auto& ocg: m_asOCGs )
352 {
353 anOFFLayers.erase(ocg.nId);
354 anONLayers.insert(ocg.nId);
355 }
356 }
357 else
358 {
359 for( const auto& ocg: m_asOCGs )
360 {
361 anONLayers.erase(ocg.nId);
362 anOFFLayers.insert(ocg.nId);
363 }
364 }
365 }
366 else if( psIter->eType == CXT_Element &&
367 strcmp(psIter->pszValue, "SetLayerStateAction") == 0 )
368 {
369 const char* pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
370 if( !pszLayerId )
371 {
372 CPLError(CE_Failure, CPLE_AppDefined,
373 "Missing layerId");
374 return false;
375 }
376 auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
377 if( oIter == m_oMapLayerIdToOCG.end() )
378 {
379 CPLError(CE_Failure, CPLE_AppDefined,
380 "Referencing layer of unknown id: %s", pszLayerId);
381 return false;
382 }
383 const auto& ocg = oIter->second;
384
385 if( CPLTestBool(CPLGetXMLValue(psIter, "visible", "true")) )
386 {
387 anOFFLayers.erase(ocg);
388 anONLayers.insert(ocg);
389 }
390 else
391 {
392 anONLayers.erase(ocg);
393 anOFFLayers.insert(ocg);
394 }
395 }
396 else if( psIter->eType == CXT_Element &&
397 strcmp(psIter->pszValue, "JavascriptAction") == 0 )
398 {
399 std::unique_ptr<JavascriptAction> poAction(new JavascriptAction());
400 poAction->m_osScript = CPLGetXMLValue(psIter, nullptr, "");
401 actions.push_back(std::move(poAction));
402 }
403 }
404
405 if( !anONLayers.empty() || !anOFFLayers.empty() )
406 {
407 std::unique_ptr<SetLayerStateAction> poAction(new SetLayerStateAction());
408 poAction->m_anONLayers = std::move(anONLayers);
409 poAction->m_anOFFLayers = std::move(anOFFLayers);
410 actions.push_back(std::move(poAction));
411 }
412
413 return true;
414 }
415
416 /************************************************************************/
417 /* CreateOutlineFirstPass() */
418 /************************************************************************/
419
CreateOutlineFirstPass(const CPLXMLNode * psNode,OutlineItem * poParentItem)420 bool GDALPDFComposerWriter::CreateOutlineFirstPass(const CPLXMLNode* psNode,
421 OutlineItem* poParentItem)
422 {
423 for(const auto* psIter = psNode->psChild; psIter; psIter = psIter->psNext)
424 {
425 if( psIter->eType == CXT_Element &&
426 strcmp(psIter->pszValue, "OutlineItem") == 0 )
427 {
428 std::unique_ptr<OutlineItem> newItem(new OutlineItem());
429 const char* pszName = CPLGetXMLValue(psIter, "name", nullptr);
430 if( !pszName )
431 {
432 CPLError(CE_Failure, CPLE_AppDefined,
433 "Missing name attribute in OutlineItem");
434 return false;
435 }
436 newItem->m_osName = pszName;
437 newItem->m_bOpen =
438 CPLTestBool(CPLGetXMLValue(psIter, "open", "true"));
439 if( CPLTestBool(CPLGetXMLValue(psIter, "italic", "false")) )
440 newItem->m_nFlags |= 1 << 0;
441 if( CPLTestBool(CPLGetXMLValue(psIter, "bold", "false")) )
442 newItem->m_nFlags |= 1 << 1;
443
444 const auto poActions = CPLGetXMLNode(psIter, "Actions");
445 if( poActions )
446 {
447 if( !ParseActions(poActions, newItem->m_aoActions) )
448 return false;
449 }
450
451 newItem->m_nObjId = AllocNewObject();
452 if( !CreateOutlineFirstPass(psIter, newItem.get()) )
453 {
454 return false;
455 }
456 poParentItem->m_nKidsRecCount += 1 + newItem->m_nKidsRecCount;
457 poParentItem->m_aoKids.push_back(std::move(newItem));
458 }
459 }
460 return true;
461 }
462
463 /************************************************************************/
464 /* SerializeActions() */
465 /************************************************************************/
466
SerializeActions(GDALPDFDictionaryRW * poDictForDest,const std::vector<std::unique_ptr<Action>> & actions)467 GDALPDFDictionaryRW* GDALPDFComposerWriter::SerializeActions(
468 GDALPDFDictionaryRW* poDictForDest,
469 const std::vector<std::unique_ptr<Action>>& actions)
470 {
471 GDALPDFDictionaryRW* poRetAction = nullptr;
472 GDALPDFDictionaryRW* poLastActionDict = nullptr;
473 for( const auto& poAction: actions )
474 {
475 GDALPDFDictionaryRW* poActionDict = nullptr;
476 auto poGotoPageAction = dynamic_cast<GotoPageAction*>(poAction.get());
477 if( poGotoPageAction )
478 {
479 GDALPDFArrayRW* poDest = new GDALPDFArrayRW;
480 poDest->Add(poGotoPageAction->m_nPageDestId, 0);
481 if( poGotoPageAction->m_dfX1 == 0.0 &&
482 poGotoPageAction->m_dfX2 == 0.0 &&
483 poGotoPageAction->m_dfY1 == 0.0 &&
484 poGotoPageAction->m_dfY2 == 0.0 )
485 {
486 poDest->Add(GDALPDFObjectRW::CreateName("XYZ"))
487 .Add(GDALPDFObjectRW::CreateNull())
488 .Add(GDALPDFObjectRW::CreateNull())
489 .Add(GDALPDFObjectRW::CreateNull());
490 }
491 else
492 {
493 poDest->Add(GDALPDFObjectRW::CreateName("FitR"))
494 .Add(poGotoPageAction->m_dfX1)
495 .Add(poGotoPageAction->m_dfY1)
496 .Add(poGotoPageAction->m_dfX2)
497 .Add(poGotoPageAction->m_dfY2);
498 }
499 if( poDictForDest && actions.size() == 1 )
500 {
501 poDictForDest->Add("Dest", poDest);
502 }
503 else
504 {
505 poActionDict = new GDALPDFDictionaryRW();
506 poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
507 poActionDict->Add("S", GDALPDFObjectRW::CreateName("GoTo"));
508 poActionDict->Add("D", poDest);
509 }
510 }
511
512 auto setLayerStateAction = dynamic_cast<SetLayerStateAction*>(poAction.get());
513 if( poActionDict == nullptr && setLayerStateAction )
514 {
515 poActionDict = new GDALPDFDictionaryRW();
516 poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
517 poActionDict->Add("S", GDALPDFObjectRW::CreateName("SetOCGState"));
518 auto poStateArray = new GDALPDFArrayRW();
519 if( !setLayerStateAction->m_anOFFLayers.empty() )
520 {
521 poStateArray->Add(GDALPDFObjectRW::CreateName("OFF"));
522 for( const auto& ocg: setLayerStateAction->m_anOFFLayers )
523 poStateArray->Add(ocg, 0);
524 }
525 if( !setLayerStateAction->m_anONLayers.empty() )
526 {
527 poStateArray->Add(GDALPDFObjectRW::CreateName("ON"));
528 for( const auto& ocg: setLayerStateAction->m_anONLayers )
529 poStateArray->Add(ocg, 0);
530 }
531 poActionDict->Add("State", poStateArray);
532 }
533
534 auto javascriptAction = dynamic_cast<JavascriptAction*>(poAction.get());
535 if( poActionDict == nullptr && javascriptAction )
536 {
537 poActionDict = new GDALPDFDictionaryRW();
538 poActionDict->Add("Type", GDALPDFObjectRW::CreateName("Action"));
539 poActionDict->Add("S", GDALPDFObjectRW::CreateName("JavaScript"));
540 poActionDict->Add("JS", javascriptAction->m_osScript);
541 }
542
543 if( poActionDict )
544 {
545 if( poLastActionDict == nullptr )
546 {
547 poRetAction = poActionDict;
548 }
549 else
550 {
551 poLastActionDict->Add("Next", poActionDict);
552 }
553 poLastActionDict = poActionDict;
554 }
555 }
556 return poRetAction;
557 }
558
559 /************************************************************************/
560 /* SerializeOutlineKids() */
561 /************************************************************************/
562
SerializeOutlineKids(const OutlineItem * poParentItem)563 bool GDALPDFComposerWriter::SerializeOutlineKids(const OutlineItem* poParentItem)
564 {
565 for( size_t i = 0; i < poParentItem->m_aoKids.size(); i++ )
566 {
567 const auto& poItem = poParentItem->m_aoKids[i];
568 StartObj(poItem->m_nObjId);
569 GDALPDFDictionaryRW oDict;
570 oDict.Add("Title", poItem->m_osName);
571
572 auto poActionDict = SerializeActions(&oDict, poItem->m_aoActions);
573 if( poActionDict )
574 {
575 oDict.Add("A", poActionDict);
576 }
577
578 if( i > 0 )
579 {
580 oDict.Add("Prev", poParentItem->m_aoKids[i-1]->m_nObjId, 0);
581 }
582 if( i + 1 < poParentItem->m_aoKids.size() )
583 {
584 oDict.Add("Next", poParentItem->m_aoKids[i+1]->m_nObjId, 0);
585 }
586 if( poItem->m_nFlags )
587 oDict.Add("F", poItem->m_nFlags);
588 oDict.Add("Parent", poParentItem->m_nObjId, 0);
589 if( !poItem->m_aoKids.empty() )
590 {
591 oDict.Add("First", poItem->m_aoKids.front()->m_nObjId, 0);
592 oDict.Add("Last", poItem->m_aoKids.back()->m_nObjId, 0);
593 oDict.Add("Count", poItem->m_bOpen ?
594 poItem->m_nKidsRecCount : -poItem->m_nKidsRecCount);
595 }
596 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
597 EndObj();
598 SerializeOutlineKids(poItem.get());
599 }
600 return true;
601 }
602
603 /************************************************************************/
604 /* CreateOutline() */
605 /************************************************************************/
606
CreateOutline(const CPLXMLNode * psNode)607 bool GDALPDFComposerWriter::CreateOutline(const CPLXMLNode* psNode)
608 {
609 OutlineItem oRootOutlineItem;
610 if( !CreateOutlineFirstPass(psNode, &oRootOutlineItem) )
611 return false;
612 if( oRootOutlineItem.m_aoKids.empty() )
613 return true;
614
615 m_nOutlinesId = AllocNewObject();
616 StartObj(m_nOutlinesId);
617 GDALPDFDictionaryRW oDict;
618 oDict.Add("Type", GDALPDFObjectRW::CreateName("Outlines"))
619 .Add("First", oRootOutlineItem.m_aoKids.front()->m_nObjId, 0)
620 .Add("Last", oRootOutlineItem.m_aoKids.back()->m_nObjId, 0)
621 .Add("Count", oRootOutlineItem.m_nKidsRecCount);
622 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
623 EndObj();
624 oRootOutlineItem.m_nObjId = m_nOutlinesId;
625 return SerializeOutlineKids(&oRootOutlineItem);
626 }
627
628 /************************************************************************/
629 /* GenerateGeoreferencing() */
630 /************************************************************************/
631
632
GenerateGeoreferencing(const CPLXMLNode * psGeoreferencing,double dfWidthInUserUnit,double dfHeightInUserUnit,GDALPDFObjectNum & nViewportId,GDALPDFObjectNum & nLGIDictId,Georeferencing & georeferencing)633 bool GDALPDFComposerWriter::GenerateGeoreferencing(const CPLXMLNode* psGeoreferencing,
634 double dfWidthInUserUnit,
635 double dfHeightInUserUnit,
636 GDALPDFObjectNum& nViewportId,
637 GDALPDFObjectNum& nLGIDictId,
638 Georeferencing& georeferencing)
639 {
640 double bboxX1 = 0;
641 double bboxY1 = 0;
642 double bboxX2 = dfWidthInUserUnit;
643 double bboxY2 = dfHeightInUserUnit;
644 const auto psBoundingBox = CPLGetXMLNode(psGeoreferencing, "BoundingBox");
645 if( psBoundingBox )
646 {
647 bboxX1 = CPLAtof(
648 CPLGetXMLValue(psBoundingBox, "x1", CPLSPrintf("%.18g", bboxX1)));
649 bboxY1 = CPLAtof(
650 CPLGetXMLValue(psBoundingBox, "y1", CPLSPrintf("%.18g", bboxY1)));
651 bboxX2 = CPLAtof(
652 CPLGetXMLValue(psBoundingBox, "x2", CPLSPrintf("%.18g", bboxX2)));
653 bboxY2 = CPLAtof(
654 CPLGetXMLValue(psBoundingBox, "y2", CPLSPrintf("%.18g", bboxY2)));
655 if( bboxX2 <= bboxX1 || bboxY2 <= bboxY1 )
656 {
657 CPLError(CE_Failure, CPLE_AppDefined, "Invalid BoundingBox");
658 return false;
659 }
660 }
661
662 std::vector<GDAL_GCP> aGCPs;
663 for(const auto* psIter = psGeoreferencing->psChild;
664 psIter; psIter = psIter->psNext)
665 {
666 if( psIter->eType == CXT_Element &&
667 strcmp(psIter->pszValue, "ControlPoint") == 0 )
668 {
669 const char* pszx = CPLGetXMLValue(psIter, "x", nullptr);
670 const char* pszy = CPLGetXMLValue(psIter, "y", nullptr);
671 const char* pszX = CPLGetXMLValue(psIter, "GeoX", nullptr);
672 const char* pszY = CPLGetXMLValue(psIter, "GeoY", nullptr);
673 if( !pszx || !pszy || !pszX || !pszY )
674 {
675 CPLError(CE_Failure, CPLE_NotSupported,
676 "At least one of x, y, GeoX or GeoY attribute "
677 "missing on ControlPoint");
678 return false;
679 }
680 GDAL_GCP gcp;
681 gcp.pszId = nullptr;
682 gcp.pszInfo = nullptr;
683 gcp.dfGCPPixel = CPLAtof(pszx);
684 gcp.dfGCPLine = CPLAtof(pszy);
685 gcp.dfGCPX = CPLAtof(pszX);
686 gcp.dfGCPY = CPLAtof(pszY);
687 gcp.dfGCPZ = 0;
688 aGCPs.emplace_back(std::move(gcp));
689 }
690 }
691 if( aGCPs.size() < 4 )
692 {
693 CPLError(CE_Failure, CPLE_NotSupported,
694 "At least 4 ControlPoint are required");
695 return false;
696 }
697
698 const char* pszBoundingPolygon =
699 CPLGetXMLValue(psGeoreferencing, "BoundingPolygon", nullptr);
700 std::vector<xyPair> aBoundingPolygon;
701 if( pszBoundingPolygon )
702 {
703 OGRGeometry* poGeom = nullptr;
704 OGRGeometryFactory::createFromWkt(pszBoundingPolygon, nullptr, &poGeom);
705 if( poGeom && poGeom->getGeometryType() == wkbPolygon )
706 {
707 auto poPoly = poGeom->toPolygon();
708 auto poRing = poPoly->getExteriorRing();
709 if( poRing )
710 {
711 if( psBoundingBox == nullptr )
712 {
713 OGREnvelope sEnvelope;
714 poRing->getEnvelope(&sEnvelope);
715 bboxX1 = sEnvelope.MinX;
716 bboxY1 = sEnvelope.MinY;
717 bboxX2 = sEnvelope.MaxX;
718 bboxY2 = sEnvelope.MaxY;
719 }
720 for( int i = 0; i < poRing->getNumPoints(); i++ )
721 {
722 aBoundingPolygon.emplace_back(
723 xyPair(poRing->getX(i), poRing->getY(i)));
724 }
725 }
726 }
727 delete poGeom;
728 }
729
730 const auto pszSRS = CPLGetXMLValue(psGeoreferencing, "SRS", nullptr);
731 if( !pszSRS )
732 {
733 CPLError(CE_Failure, CPLE_NotSupported,
734 "Missing SRS");
735 return false;
736 }
737 std::unique_ptr<OGRSpatialReference> poSRS(new OGRSpatialReference());
738 if( poSRS->SetFromUserInput(pszSRS) != OGRERR_NONE )
739 {
740 return false;
741 }
742 poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
743
744 if( CPLTestBool(CPLGetXMLValue(psGeoreferencing, "ISO32000ExtensionFormat", "true")) )
745 {
746 nViewportId = GenerateISO32000_Georeferencing(
747 OGRSpatialReference::ToHandle(poSRS.get()),
748 bboxX1, bboxY1, bboxX2, bboxY2, aGCPs, aBoundingPolygon);
749 if( !nViewportId.toBool() )
750 {
751 return false;
752 }
753 }
754
755 if( CPLTestBool(CPLGetXMLValue(psGeoreferencing, "OGCBestPracticeFormat", "false")) )
756 {
757 nLGIDictId = GenerateOGC_BP_Georeferencing(
758 OGRSpatialReference::ToHandle(poSRS.get()),
759 bboxX1, bboxY1, bboxX2, bboxY2, aGCPs, aBoundingPolygon);
760 if( !nLGIDictId.toBool() )
761 {
762 return false;
763 }
764 }
765
766 const char* pszId = CPLGetXMLValue(psGeoreferencing, "id", nullptr);
767 if( pszId )
768 {
769 if (!GDALGCPsToGeoTransform( static_cast<int>(aGCPs.size()),
770 aGCPs.data(),
771 georeferencing.m_adfGT, TRUE))
772 {
773 CPLError(CE_Failure, CPLE_AppDefined,
774 "Could not compute geotransform with approximate match.");
775 return false;
776 }
777 if( std::fabs(georeferencing.m_adfGT[2]) < 1e-5 *
778 std::fabs(georeferencing.m_adfGT[1]) &&
779 std::fabs(georeferencing.m_adfGT[4]) < 1e-5 *
780 std::fabs(georeferencing.m_adfGT[5]) )
781 {
782 georeferencing.m_adfGT[2] = 0;
783 georeferencing.m_adfGT[4] = 0;
784 }
785 if( georeferencing.m_adfGT[2] != 0 ||
786 georeferencing.m_adfGT[4] != 0 ||
787 georeferencing.m_adfGT[5] < 0 )
788 {
789 CPLError(CE_Failure, CPLE_AppDefined,
790 "Geotransform should define a north-up non rotated area.");
791 return false;
792 }
793 georeferencing.m_osID = pszId;
794 georeferencing.m_oSRS = *(poSRS.get());
795 georeferencing.m_bboxX1 = bboxX1;
796 georeferencing.m_bboxY1 = bboxY1;
797 georeferencing.m_bboxX2 = bboxX2;
798 georeferencing.m_bboxY2 = bboxY2;
799 }
800
801 return true;
802 }
803
804 /************************************************************************/
805 /* GenerateISO32000_Georeferencing() */
806 /************************************************************************/
807
GenerateISO32000_Georeferencing(OGRSpatialReferenceH hSRS,double bboxX1,double bboxY1,double bboxX2,double bboxY2,const std::vector<GDAL_GCP> & aGCPs,const std::vector<xyPair> & aBoundingPolygon)808 GDALPDFObjectNum GDALPDFComposerWriter::GenerateISO32000_Georeferencing(
809 OGRSpatialReferenceH hSRS,
810 double bboxX1, double bboxY1, double bboxX2, double bboxY2,
811 const std::vector<GDAL_GCP>& aGCPs,
812 const std::vector<xyPair>& aBoundingPolygon)
813 {
814 OGRSpatialReferenceH hSRSGeog = OSRCloneGeogCS(hSRS);
815 if( hSRSGeog == nullptr )
816 {
817 return GDALPDFObjectNum();
818 }
819 OSRSetAxisMappingStrategy(hSRSGeog, OAMS_TRADITIONAL_GIS_ORDER);
820 OGRCoordinateTransformationH hCT = OCTNewCoordinateTransformation( hSRS, hSRSGeog);
821 if( hCT == nullptr )
822 {
823 OSRDestroySpatialReference(hSRSGeog);
824 return GDALPDFObjectNum();
825 }
826
827 std::vector<GDAL_GCP> aGCPReprojected;
828 bool bSuccess = true;
829 for( const auto& gcp: aGCPs )
830 {
831 double X = gcp.dfGCPX;
832 double Y = gcp.dfGCPY;
833 bSuccess &= OCTTransform( hCT, 1,&X, &Y, nullptr ) == 1;
834 GDAL_GCP newGCP;
835 newGCP.pszId = nullptr;
836 newGCP.pszInfo = nullptr;
837 newGCP.dfGCPPixel = gcp.dfGCPPixel;
838 newGCP.dfGCPLine = gcp.dfGCPLine;
839 newGCP.dfGCPX = X;
840 newGCP.dfGCPY = Y;
841 newGCP.dfGCPZ = 0;
842 aGCPReprojected.emplace_back(std::move(newGCP));
843 }
844 if( !bSuccess )
845 {
846 OSRDestroySpatialReference(hSRSGeog);
847 OCTDestroyCoordinateTransformation(hCT);
848
849 return GDALPDFObjectNum();
850 }
851
852 const char * pszAuthorityCode = OSRGetAuthorityCode( hSRS, nullptr );
853 const char * pszAuthorityName = OSRGetAuthorityName( hSRS, nullptr );
854 int nEPSGCode = 0;
855 if( pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG") &&
856 pszAuthorityCode != nullptr )
857 nEPSGCode = atoi(pszAuthorityCode);
858
859 int bIsGeographic = OSRIsGeographic(hSRS);
860
861 char* pszESRIWKT = nullptr;
862 const char* apszOptions[] = { "FORMAT=WKT1_ESRI", nullptr };
863 OSRExportToWktEx(hSRS, &pszESRIWKT, apszOptions);
864
865 OSRDestroySpatialReference(hSRSGeog);
866 OCTDestroyCoordinateTransformation(hCT);
867
868 auto nViewportId = AllocNewObject();
869 auto nMeasureId = AllocNewObject();
870 auto nGCSId = AllocNewObject();
871
872 StartObj(nViewportId);
873 GDALPDFDictionaryRW oViewPortDict;
874 oViewPortDict.Add("Type", GDALPDFObjectRW::CreateName("Viewport"))
875 .Add("Name", "Layer")
876 .Add("BBox", &((new GDALPDFArrayRW())
877 ->Add(bboxX1).Add(bboxY1)
878 .Add(bboxX2).Add(bboxY2)))
879 .Add("Measure", nMeasureId, 0);
880 VSIFPrintfL(m_fp, "%s\n", oViewPortDict.Serialize().c_str());
881 EndObj();
882
883 GDALPDFArrayRW* poGPTS = new GDALPDFArrayRW();
884 GDALPDFArrayRW* poLPTS = new GDALPDFArrayRW();
885
886 const int nPrecision =
887 atoi(CPLGetConfigOption("PDF_COORD_DOUBLE_PRECISION", "16"));
888 for( const auto& gcp: aGCPReprojected )
889 {
890 poGPTS->AddWithPrecision(gcp.dfGCPY, nPrecision).
891 AddWithPrecision(gcp.dfGCPX, nPrecision); // Lat, long order
892 poLPTS->AddWithPrecision((gcp.dfGCPPixel - bboxX1) / (bboxX2 - bboxX1), nPrecision).
893 AddWithPrecision((gcp.dfGCPLine - bboxY1) / (bboxY2 - bboxY1), nPrecision);
894 }
895
896 StartObj(nMeasureId);
897 GDALPDFDictionaryRW oMeasureDict;
898 oMeasureDict .Add("Type", GDALPDFObjectRW::CreateName("Measure"))
899 .Add("Subtype", GDALPDFObjectRW::CreateName("GEO"))
900 .Add("GPTS", poGPTS)
901 .Add("LPTS", poLPTS)
902 .Add("GCS", nGCSId, 0);
903 if( !aBoundingPolygon.empty() )
904 {
905 GDALPDFArrayRW* poBounds = new GDALPDFArrayRW();
906 for( const auto& xy: aBoundingPolygon )
907 {
908 poBounds->Add((xy.x - bboxX1) / (bboxX2 - bboxX1)).
909 Add((xy.y - bboxY1) / (bboxY2 - bboxY1));
910 }
911 oMeasureDict.Add("Bounds", poBounds);
912 }
913 VSIFPrintfL(m_fp, "%s\n", oMeasureDict.Serialize().c_str());
914 EndObj();
915
916 StartObj(nGCSId);
917 GDALPDFDictionaryRW oGCSDict;
918 oGCSDict.Add("Type", GDALPDFObjectRW::CreateName(bIsGeographic ? "GEOGCS" : "PROJCS"))
919 .Add("WKT", pszESRIWKT);
920 if (nEPSGCode)
921 oGCSDict.Add("EPSG", nEPSGCode);
922 VSIFPrintfL(m_fp, "%s\n", oGCSDict.Serialize().c_str());
923 EndObj();
924
925 CPLFree(pszESRIWKT);
926
927 return nViewportId;
928 }
929
930 /************************************************************************/
931 /* GenerateOGC_BP_Georeferencing() */
932 /************************************************************************/
933
GenerateOGC_BP_Georeferencing(OGRSpatialReferenceH hSRS,double bboxX1,double bboxY1,double bboxX2,double bboxY2,const std::vector<GDAL_GCP> & aGCPs,const std::vector<xyPair> & aBoundingPolygon)934 GDALPDFObjectNum GDALPDFComposerWriter::GenerateOGC_BP_Georeferencing(
935 OGRSpatialReferenceH hSRS,
936 double bboxX1, double bboxY1, double bboxX2, double bboxY2,
937 const std::vector<GDAL_GCP>& aGCPs,
938 const std::vector<xyPair>& aBoundingPolygon)
939 {
940 const OGRSpatialReference* poSRS = OGRSpatialReference::FromHandle(hSRS);
941 GDALPDFDictionaryRW* poProjectionDict = GDALPDFBuildOGC_BP_Projection(poSRS);
942 if (poProjectionDict == nullptr)
943 {
944 OSRDestroySpatialReference(hSRS);
945 return GDALPDFObjectNum();
946 }
947
948 GDALPDFArrayRW* poNeatLineArray = new GDALPDFArrayRW();
949 if( !aBoundingPolygon.empty() )
950 {
951 for( const auto& xy: aBoundingPolygon )
952 {
953 poNeatLineArray->Add(xy.x).Add(xy.y);
954 }
955 }
956 else
957 {
958 poNeatLineArray->Add(bboxX1).Add(bboxY1).
959 Add(bboxX2).Add(bboxY2);
960 }
961
962 GDALPDFArrayRW* poRegistration = new GDALPDFArrayRW();
963
964 for( const auto& gcp: aGCPs )
965 {
966 GDALPDFArrayRW* poGCP = new GDALPDFArrayRW();
967 poGCP->Add(gcp.dfGCPPixel, TRUE).Add(gcp.dfGCPLine, TRUE).
968 Add(gcp.dfGCPX, TRUE).Add(gcp.dfGCPY, TRUE);
969 poRegistration->Add(poGCP);
970 }
971
972 auto nLGIDictId = AllocNewObject();
973 StartObj(nLGIDictId);
974 GDALPDFDictionaryRW oLGIDict;
975 oLGIDict.Add("Type", GDALPDFObjectRW::CreateName("LGIDict"))
976 .Add("Version", "2.1")
977 .Add("Neatline", poNeatLineArray);
978
979 oLGIDict.Add("Registration", poRegistration);
980
981 /* GDAL extension */
982 if( CPLTestBool( CPLGetConfigOption("GDAL_PDF_OGC_BP_WRITE_WKT", "TRUE") ) )
983 {
984 char* pszWKT = nullptr;
985 OSRExportToWkt(hSRS, &pszWKT);
986 if( pszWKT )
987 poProjectionDict->Add("WKT", pszWKT);
988 CPLFree(pszWKT);
989 }
990
991 oLGIDict.Add("Projection", poProjectionDict);
992
993 VSIFPrintfL(m_fp, "%s\n", oLGIDict.Serialize().c_str());
994 EndObj();
995
996 return nLGIDictId;
997 }
998
999 /************************************************************************/
1000 /* GeneratePage() */
1001 /************************************************************************/
1002
1003
GeneratePage(const CPLXMLNode * psPage)1004 bool GDALPDFComposerWriter::GeneratePage(const CPLXMLNode* psPage)
1005 {
1006 double dfWidthInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Width", "-1"));
1007 double dfHeightInUserUnit = CPLAtof(CPLGetXMLValue(psPage, "Height", "-1"));
1008 if( dfWidthInUserUnit <= 0 || dfWidthInUserUnit >= MAXIMUM_SIZE_IN_UNITS ||
1009 dfHeightInUserUnit <= 0 || dfHeightInUserUnit >= MAXIMUM_SIZE_IN_UNITS )
1010 {
1011 CPLError(CE_Failure, CPLE_AppDefined,
1012 "Missing or invalid Width and/or Height");
1013 return false;
1014 }
1015 double dfUserUnit = CPLAtof(CPLGetXMLValue(psPage, "DPI",
1016 CPLSPrintf("%f", DEFAULT_DPI))) * USER_UNIT_IN_INCH;
1017
1018 std::vector<GDALPDFObjectNum> anViewportIds;
1019 std::vector<GDALPDFObjectNum> anLGIDictIds;
1020
1021 PageContext oPageContext;
1022 for(const auto* psIter = psPage->psChild; psIter; psIter = psIter->psNext)
1023 {
1024 if( psIter->eType == CXT_Element &&
1025 strcmp(psIter->pszValue, "Georeferencing") == 0 )
1026 {
1027 GDALPDFObjectNum nViewportId;
1028 GDALPDFObjectNum nLGIDictId;
1029 Georeferencing georeferencing;
1030 if( !GenerateGeoreferencing(psIter,
1031 dfWidthInUserUnit, dfHeightInUserUnit,
1032 nViewportId, nLGIDictId,
1033 georeferencing) )
1034 {
1035 return false;
1036 }
1037 if( nViewportId.toBool() )
1038 anViewportIds.emplace_back(nViewportId);
1039 if( nLGIDictId.toBool() )
1040 anLGIDictIds.emplace_back(nLGIDictId);
1041 if( !georeferencing.m_osID.empty() )
1042 {
1043 oPageContext.m_oMapGeoreferencedId[georeferencing.m_osID] =
1044 georeferencing;
1045 }
1046 }
1047 }
1048
1049 auto nPageId = AllocNewObject();
1050 m_asPageId.push_back(nPageId);
1051
1052 const char* pszId = CPLGetXMLValue(psPage, "id", nullptr);
1053 if( pszId )
1054 {
1055 if( m_oMapPageIdToObjectNum.find(pszId) != m_oMapPageIdToObjectNum.end() )
1056 {
1057 CPLError(CE_Failure, CPLE_AppDefined,
1058 "Duplicated page id %s", pszId);
1059 return false;
1060 }
1061 m_oMapPageIdToObjectNum[pszId] = nPageId;
1062 }
1063
1064 const auto psContent = CPLGetXMLNode(psPage, "Content");
1065 if( !psContent )
1066 {
1067 CPLError(CE_Failure, CPLE_AppDefined, "Missing Content");
1068 return false;
1069 }
1070
1071 const bool bDeflateStreamCompression = EQUAL(
1072 CPLGetXMLValue(psContent, "streamCompression", "DEFLATE"), "DEFLATE");
1073
1074 oPageContext.m_dfWidthInUserUnit = dfWidthInUserUnit;
1075 oPageContext.m_dfHeightInUserUnit = dfHeightInUserUnit;
1076 oPageContext.m_eStreamCompressMethod =
1077 bDeflateStreamCompression ? COMPRESS_DEFLATE : COMPRESS_NONE;
1078 if( !ExploreContent(psContent, oPageContext) )
1079 return false;
1080
1081 int nStructParentsIdx = -1;
1082 if( !oPageContext.m_anFeatureUserProperties.empty() )
1083 {
1084 nStructParentsIdx = static_cast<int>(m_anParentElements.size());
1085 auto nParentsElements = AllocNewObject();
1086 m_anParentElements.push_back(nParentsElements);
1087 {
1088 StartObj(nParentsElements);
1089 VSIFPrintfL(m_fp, "[ ");
1090 for( const auto& num: oPageContext.m_anFeatureUserProperties )
1091 VSIFPrintfL(m_fp, "%d 0 R ", num.toInt());
1092 VSIFPrintfL(m_fp, " ]\n");
1093 EndObj();
1094 }
1095 }
1096
1097 GDALPDFObjectNum nAnnotsId;
1098 if( !oPageContext.m_anAnnotationsId.empty() )
1099 {
1100 /* -------------------------------------------------------------- */
1101 /* Write annotation arrays. */
1102 /* -------------------------------------------------------------- */
1103 nAnnotsId = AllocNewObject();
1104 StartObj(nAnnotsId);
1105 {
1106 GDALPDFArrayRW oArray;
1107 for(size_t i = 0; i < oPageContext.m_anAnnotationsId.size(); i++)
1108 {
1109 oArray.Add(oPageContext.m_anAnnotationsId[i], 0);
1110 }
1111 VSIFPrintfL(m_fp, "%s\n", oArray.Serialize().c_str());
1112 }
1113 EndObj();
1114 }
1115
1116 auto nContentId = AllocNewObject();
1117 auto nResourcesId = AllocNewObject();
1118
1119 StartObj(nPageId);
1120 GDALPDFDictionaryRW oDictPage;
1121 oDictPage.Add("Type", GDALPDFObjectRW::CreateName("Page"))
1122 .Add("Parent", m_nPageResourceId, 0)
1123 .Add("MediaBox", &((new GDALPDFArrayRW())
1124 ->Add(0).Add(0).
1125 Add(dfWidthInUserUnit).
1126 Add(dfHeightInUserUnit)))
1127 .Add("UserUnit", dfUserUnit)
1128 .Add("Contents", nContentId, 0)
1129 .Add("Resources", nResourcesId, 0);
1130
1131 if( nAnnotsId.toBool() )
1132 oDictPage.Add("Annots", nAnnotsId, 0);
1133
1134 oDictPage.Add("Group",
1135 &((new GDALPDFDictionaryRW())
1136 ->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1137 .Add("S", GDALPDFObjectRW::CreateName("Transparency"))
1138 .Add("CS", GDALPDFObjectRW::CreateName("DeviceRGB"))));
1139 if (!anViewportIds.empty())
1140 {
1141 auto poViewports = new GDALPDFArrayRW();
1142 for( const auto& id: anViewportIds )
1143 poViewports->Add(id, 0);
1144 oDictPage.Add("VP", poViewports);
1145 }
1146
1147 if (anLGIDictIds.size() == 1 )
1148 {
1149 oDictPage.Add("LGIDict", anLGIDictIds[0], 0);
1150 }
1151 else if (!anLGIDictIds.empty())
1152 {
1153 auto poLGIDict = new GDALPDFArrayRW();
1154 for( const auto& id: anLGIDictIds )
1155 poLGIDict->Add(id, 0);
1156 oDictPage.Add("LGIDict", poLGIDict);
1157 }
1158
1159 if( nStructParentsIdx >= 0 )
1160 {
1161 oDictPage.Add("StructParents", nStructParentsIdx);
1162 }
1163
1164 VSIFPrintfL(m_fp, "%s\n", oDictPage.Serialize().c_str());
1165 EndObj();
1166
1167 /* -------------------------------------------------------------- */
1168 /* Write content dictionary */
1169 /* -------------------------------------------------------------- */
1170 {
1171 GDALPDFDictionaryRW oDict;
1172 StartObjWithStream(nContentId, oDict, bDeflateStreamCompression);
1173 VSIFPrintfL(m_fp, "%s", oPageContext.m_osDrawingStream.c_str());
1174 EndObjWithStream();
1175 }
1176
1177 /* -------------------------------------------------------------- */
1178 /* Write page resource dictionary. */
1179 /* -------------------------------------------------------------- */
1180 StartObj(nResourcesId);
1181 {
1182 GDALPDFDictionaryRW oDict;
1183 if( !oPageContext.m_oXObjects.empty() )
1184 {
1185 GDALPDFDictionaryRW* poDict = new GDALPDFDictionaryRW();
1186 for( const auto&kv: oPageContext.m_oXObjects )
1187 {
1188 poDict->Add(kv.first, kv.second, 0);
1189 }
1190 oDict.Add("XObject", poDict);
1191 }
1192
1193 if( !oPageContext.m_oProperties.empty() )
1194 {
1195 GDALPDFDictionaryRW* poDict = new GDALPDFDictionaryRW();
1196 for( const auto&kv: oPageContext.m_oProperties )
1197 {
1198 poDict->Add(kv.first, kv.second, 0);
1199 }
1200 oDict.Add("Properties", poDict);
1201 }
1202
1203 if( !oPageContext.m_oExtGState.empty() )
1204 {
1205 GDALPDFDictionaryRW* poDict = new GDALPDFDictionaryRW();
1206 for( const auto&kv: oPageContext.m_oExtGState )
1207 {
1208 poDict->Add(kv.first, kv.second, 0);
1209 }
1210 oDict.Add("ExtGState", poDict);
1211 }
1212
1213 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1214 }
1215 EndObj();
1216
1217 return true;
1218 }
1219
1220 /************************************************************************/
1221 /* ExploreContent() */
1222 /************************************************************************/
1223
ExploreContent(const CPLXMLNode * psNode,PageContext & oPageContext)1224 bool GDALPDFComposerWriter::ExploreContent(const CPLXMLNode* psNode,
1225 PageContext& oPageContext)
1226 {
1227 for(const auto* psIter = psNode->psChild; psIter; psIter = psIter->psNext)
1228 {
1229 if( psIter->eType == CXT_Element &&
1230 strcmp(psIter->pszValue, "IfLayerOn") == 0 )
1231 {
1232 const char* pszLayerId = CPLGetXMLValue(psIter, "layerId", nullptr);
1233 if( !pszLayerId )
1234 {
1235 CPLError(CE_Failure, CPLE_AppDefined,
1236 "Missing layerId");
1237 return false;
1238 }
1239 auto oIter = m_oMapLayerIdToOCG.find(pszLayerId);
1240 if( oIter == m_oMapLayerIdToOCG.end() )
1241 {
1242 CPLError(CE_Failure, CPLE_AppDefined,
1243 "Referencing layer of unknown id: %s", pszLayerId);
1244 return false;
1245 }
1246 oPageContext.m_oProperties[
1247 CPLOPrintf("Lyr%d", oIter->second.toInt())] = oIter->second;
1248 oPageContext.m_osDrawingStream +=
1249 CPLOPrintf("/OC /Lyr%d BDC\n", oIter->second.toInt());
1250 if( !ExploreContent(psIter, oPageContext) )
1251 return false;
1252 oPageContext.m_osDrawingStream += "EMC\n";
1253 }
1254
1255 else if( psIter->eType == CXT_Element &&
1256 strcmp(psIter->pszValue, "Raster") == 0 )
1257 {
1258 if( !WriteRaster(psIter, oPageContext) )
1259 return false;
1260 }
1261
1262 else if( psIter->eType == CXT_Element &&
1263 strcmp(psIter->pszValue, "Vector") == 0 )
1264 {
1265 if( !WriteVector(psIter, oPageContext) )
1266 return false;
1267 }
1268
1269 else if( psIter->eType == CXT_Element &&
1270 strcmp(psIter->pszValue, "VectorLabel") == 0 )
1271 {
1272 if( !WriteVectorLabel(psIter, oPageContext) )
1273 return false;
1274 }
1275
1276 else if( psIter->eType == CXT_Element &&
1277 strcmp(psIter->pszValue, "PDF") == 0 )
1278 {
1279 #ifdef HAVE_PDF_READ_SUPPORT
1280 if( !WritePDF(psIter, oPageContext) )
1281 return false;
1282 #else
1283 CPLError(CE_Failure, CPLE_NotSupported,
1284 "PDF node not supported due to missing PDF read support in this GDAL build");
1285 return false;
1286 #endif
1287 }
1288 }
1289 return true;
1290 }
1291
1292 /************************************************************************/
1293 /* StartBlending() */
1294 /************************************************************************/
1295
StartBlending(const CPLXMLNode * psNode,PageContext & oPageContext,double & dfOpacity)1296 void GDALPDFComposerWriter::StartBlending(const CPLXMLNode* psNode,
1297 PageContext& oPageContext,
1298 double& dfOpacity)
1299 {
1300 dfOpacity = 1;
1301 const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1302 if( psBlending )
1303 {
1304 auto nExtGState = AllocNewObject();
1305 StartObj(nExtGState);
1306 {
1307 GDALPDFDictionaryRW gs;
1308 gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1309 dfOpacity = CPLAtof(CPLGetXMLValue(
1310 psBlending, "opacity", "1"));
1311 gs.Add("ca", dfOpacity);
1312 gs.Add("BM", GDALPDFObjectRW::CreateName(
1313 CPLGetXMLValue(psBlending, "function", "Normal")));
1314 VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1315 }
1316 EndObj();
1317 oPageContext.m_oExtGState[
1318 CPLOPrintf("GS%d", nExtGState.toInt())] = nExtGState;
1319 oPageContext.m_osDrawingStream += "q\n";
1320 oPageContext.m_osDrawingStream +=
1321 CPLOPrintf("/GS%d gs\n", nExtGState.toInt());
1322 }
1323 }
1324
1325 /************************************************************************/
1326 /* EndBlending() */
1327 /************************************************************************/
1328
EndBlending(const CPLXMLNode * psNode,PageContext & oPageContext)1329 void GDALPDFComposerWriter::EndBlending(const CPLXMLNode* psNode,
1330 PageContext& oPageContext)
1331 {
1332 const auto psBlending = CPLGetXMLNode(psNode, "Blending");
1333 if( psBlending )
1334 {
1335 oPageContext.m_osDrawingStream += "Q\n";
1336 }
1337 }
1338
1339 /************************************************************************/
1340 /* WriteRaster() */
1341 /************************************************************************/
1342
WriteRaster(const CPLXMLNode * psNode,PageContext & oPageContext)1343 bool GDALPDFComposerWriter::WriteRaster(const CPLXMLNode* psNode,
1344 PageContext& oPageContext)
1345 {
1346 const char* pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1347 if( !pszDataset )
1348 {
1349 CPLError(CE_Failure, CPLE_AppDefined,
1350 "Missing dataset");
1351 return false;
1352 }
1353 double dfX1 = CPLAtof(CPLGetXMLValue(psNode, "x1", "0"));
1354 double dfY1 = CPLAtof(CPLGetXMLValue(psNode, "y1", "0"));
1355 double dfX2 = CPLAtof(CPLGetXMLValue(psNode, "x2",
1356 CPLSPrintf("%.18g", oPageContext.m_dfWidthInUserUnit)));
1357 double dfY2 = CPLAtof(CPLGetXMLValue(psNode, "y2",
1358 CPLSPrintf("%.18g", oPageContext.m_dfHeightInUserUnit)));
1359 if( dfX2 <= dfX1 || dfY2 <= dfY1 )
1360 {
1361 CPLError(CE_Failure, CPLE_AppDefined, "Invalid x1,y1,x2,y2");
1362 return false;
1363 }
1364 GDALDatasetUniquePtr poDS(GDALDataset::Open(
1365 pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1366 nullptr, nullptr, nullptr));
1367 if( !poDS )
1368 return false;
1369 const int nWidth = poDS->GetRasterXSize();
1370 const int nHeight = poDS->GetRasterYSize();
1371 const int nBlockXSize = std::max(16,
1372 atoi(CPLGetXMLValue(psNode, "tileSize", "256")));
1373 const int nBlockYSize = nBlockXSize;
1374 const char* pszCompressMethod = CPLGetXMLValue(
1375 psNode, "Compression.method", "DEFLATE");
1376 PDFCompressMethod eCompressMethod = COMPRESS_DEFLATE;
1377 if( EQUAL(pszCompressMethod, "JPEG") )
1378 eCompressMethod = COMPRESS_JPEG;
1379 else if( EQUAL(pszCompressMethod, "JPEG2000") )
1380 eCompressMethod = COMPRESS_JPEG2000;
1381 const int nPredictor = CPLTestBool(CPLGetXMLValue(
1382 psNode, "Compression.predictor", "false")) ? 2 : 0;
1383 const int nJPEGQuality = atoi(
1384 CPLGetXMLValue(psNode, "Compression.quality", "-1"));
1385 const char* pszJPEG2000_DRIVER = m_osJPEG2000Driver.empty() ?
1386 nullptr : m_osJPEG2000Driver.c_str();;
1387
1388 const char* pszGeoreferencingId =
1389 CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1390 double dfClippingMinX = 0;
1391 double dfClippingMinY = 0;
1392 double dfClippingMaxX = 0;
1393 double dfClippingMaxY = 0;
1394 bool bClip = false;
1395 double adfRasterGT[6] = {0,1,0,0,0,1};
1396 double adfInvGeoreferencingGT[6]; // from georeferenced to PDF coordinates
1397 if( pszGeoreferencingId )
1398 {
1399 auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1400 if( iter == oPageContext.m_oMapGeoreferencedId.end() )
1401 {
1402 CPLError(CE_Failure, CPLE_AppDefined,
1403 "Cannot find georeferencing of id %s",
1404 pszGeoreferencingId);
1405 return false;
1406 }
1407 auto& georeferencing = iter->second;
1408 dfX1 = georeferencing.m_bboxX1;
1409 dfY1 = georeferencing.m_bboxY1;
1410 dfX2 = georeferencing.m_bboxX2;
1411 dfY2 = georeferencing.m_bboxY2;
1412
1413 bClip = true;
1414 dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1415 dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1416 dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1417 dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1418
1419 if( poDS->GetGeoTransform(adfRasterGT) != CE_None ||
1420 adfRasterGT[2] != 0 || adfRasterGT[4] != 0 ||
1421 adfRasterGT[5] > 0 )
1422 {
1423 CPLError(CE_Failure, CPLE_AppDefined,
1424 "Raster has no geotransform or a rotated geotransform");
1425 return false;
1426 }
1427
1428 auto poSRS = poDS->GetSpatialRef();
1429 if( !poSRS || !poSRS->IsSame(&georeferencing.m_oSRS) )
1430 {
1431 CPLError(CE_Failure, CPLE_AppDefined,
1432 "Raster has no projection, or different from the one "
1433 "of the georeferencing area");
1434 return false;
1435 }
1436
1437 CPL_IGNORE_RET_VAL(
1438 GDALInvGeoTransform(georeferencing.m_adfGT,
1439 adfInvGeoreferencingGT));
1440 }
1441 const double dfRasterMinX = adfRasterGT[0];
1442 const double dfRasterMaxY = adfRasterGT[3];
1443
1444 /* Does the source image has a color table ? */
1445 const auto nColorTableId = WriteColorTable(poDS.get());
1446
1447 double dfIgnoredOpacity;
1448 StartBlending(psNode, oPageContext, dfIgnoredOpacity);
1449
1450 CPLString osGroupStream;
1451 std::vector<GDALPDFObjectNum> anImageIds;
1452
1453 const int nXBlocks = (nWidth + nBlockXSize - 1) / nBlockXSize;
1454 const int nYBlocks = (nHeight + nBlockYSize - 1) / nBlockYSize;
1455 int nBlockXOff, nBlockYOff;
1456 for(nBlockYOff = 0; nBlockYOff < nYBlocks; nBlockYOff ++)
1457 {
1458 for(nBlockXOff = 0; nBlockXOff < nXBlocks; nBlockXOff ++)
1459 {
1460 int nReqWidth =
1461 std::min(nBlockXSize, nWidth - nBlockXOff * nBlockXSize);
1462 int nReqHeight =
1463 std::min(nBlockYSize, nHeight - nBlockYOff * nBlockYSize);
1464
1465 int nX = nBlockXOff * nBlockXSize;
1466 int nY = nBlockYOff * nBlockYSize;
1467
1468 double dfXPDFOff = nX * (dfX2 - dfX1) / nWidth + dfX1;
1469 double dfYPDFOff = (nHeight - nY - nReqHeight) * (dfY2 - dfY1) / nHeight + dfY1;
1470 double dfXPDFSize = nReqWidth * (dfX2 - dfX1) / nWidth;
1471 double dfYPDFSize = nReqHeight * (dfY2 - dfY1) / nHeight;
1472
1473 if( bClip )
1474 {
1475 /* Compute extent of block to write */
1476 double dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1477 double dfBlockMaxX = adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1478 double dfBlockMinY = adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1479 double dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1480
1481 // Clip the extent of the block with the extent of the main raster.
1482 const double dfIntersectMinX =
1483 std::max(dfBlockMinX, dfClippingMinX);
1484 const double dfIntersectMinY =
1485 std::max(dfBlockMinY, dfClippingMinY);
1486 const double dfIntersectMaxX =
1487 std::min(dfBlockMaxX, dfClippingMaxX);
1488 const double dfIntersectMaxY =
1489 std::min(dfBlockMaxY, dfClippingMaxY);
1490
1491 bool bOK = false;
1492 if( dfIntersectMinX < dfIntersectMaxX &&
1493 dfIntersectMinY < dfIntersectMaxY )
1494 {
1495 /* Re-compute (x,y,width,height) subwindow of current raster from */
1496 /* the extent of the clipped block */
1497 nX = (int)((dfIntersectMinX - dfRasterMinX) / adfRasterGT[1] + 0.5);
1498 nY = (int)((dfRasterMaxY - dfIntersectMaxY) / (-adfRasterGT[5]) + 0.5);
1499 nReqWidth = (int)((dfIntersectMaxX - dfRasterMinX) / adfRasterGT[1] + 0.5) - nX;
1500 nReqHeight = (int)((dfRasterMaxY - dfIntersectMinY) / (-adfRasterGT[5]) + 0.5) - nY;
1501
1502 if( nReqWidth > 0 && nReqHeight > 0)
1503 {
1504 dfBlockMinX = adfRasterGT[0] + nX * adfRasterGT[1];
1505 dfBlockMaxX = adfRasterGT[0] + (nX + nReqWidth) * adfRasterGT[1];
1506 dfBlockMinY = adfRasterGT[3] + (nY + nReqHeight) * adfRasterGT[5];
1507 dfBlockMaxY = adfRasterGT[3] + nY * adfRasterGT[5];
1508
1509 double dfPDFX1 = APPLY_GT_X(adfInvGeoreferencingGT, dfBlockMinX, dfBlockMinY);
1510 double dfPDFY1 = APPLY_GT_Y(adfInvGeoreferencingGT, dfBlockMinX, dfBlockMinY);
1511 double dfPDFX2 = APPLY_GT_X(adfInvGeoreferencingGT, dfBlockMaxX, dfBlockMaxY);
1512 double dfPDFY2 = APPLY_GT_Y(adfInvGeoreferencingGT, dfBlockMaxX, dfBlockMaxY);
1513
1514 dfXPDFOff = dfPDFX1;
1515 dfYPDFOff = dfPDFY1;
1516 dfXPDFSize = dfPDFX2 - dfPDFX1;
1517 dfYPDFSize = dfPDFY2 - dfPDFY1;
1518 bOK = true;
1519 }
1520 }
1521 if( !bOK )
1522 {
1523 continue;
1524 }
1525 }
1526
1527 const auto nImageId = WriteBlock(poDS.get(),
1528 nX,
1529 nY,
1530 nReqWidth, nReqHeight,
1531 nColorTableId,
1532 eCompressMethod,
1533 nPredictor,
1534 nJPEGQuality,
1535 pszJPEG2000_DRIVER,
1536 nullptr,
1537 nullptr);
1538
1539 if (!nImageId.toBool())
1540 return false;
1541
1542 anImageIds.push_back(nImageId);
1543 osGroupStream += "q\n";
1544 GDALPDFObjectRW* poXSize = GDALPDFObjectRW::CreateReal(dfXPDFSize);
1545 GDALPDFObjectRW* poYSize = GDALPDFObjectRW::CreateReal(dfYPDFSize);
1546 GDALPDFObjectRW* poXOff = GDALPDFObjectRW::CreateReal(dfXPDFOff);
1547 GDALPDFObjectRW* poYOff = GDALPDFObjectRW::CreateReal(dfYPDFOff);
1548 osGroupStream += CPLOPrintf("%s 0 0 %s %s %s cm\n",
1549 poXSize->Serialize().c_str(),
1550 poYSize->Serialize().c_str(),
1551 poXOff->Serialize().c_str(),
1552 poYOff->Serialize().c_str());
1553 delete poXSize;
1554 delete poYSize;
1555 delete poXOff;
1556 delete poYOff;
1557 osGroupStream += CPLOPrintf("/Image%d Do\n", nImageId.toInt());
1558 osGroupStream += "Q\n";
1559 }
1560 }
1561
1562 if( anImageIds.size() <= 1 ||
1563 CPLGetXMLNode(psNode, "Blending") == nullptr )
1564 {
1565 for( const auto& nImageId: anImageIds )
1566 {
1567 oPageContext.m_oXObjects[
1568 CPLOPrintf("Image%d", nImageId.toInt())] = nImageId;
1569 }
1570 oPageContext.m_osDrawingStream += osGroupStream;
1571 }
1572 else
1573 {
1574 // In case several tiles are drawn with blending, use a transparency
1575 // group to avoid edge effects.
1576
1577 auto nGroupId = AllocNewObject();
1578 GDALPDFDictionaryRW oDictGroup;
1579 GDALPDFDictionaryRW* poGroup = new GDALPDFDictionaryRW();
1580 poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
1581 .Add("S",GDALPDFObjectRW::CreateName("Transparency"));
1582
1583 GDALPDFDictionaryRW* poXObjects = new GDALPDFDictionaryRW();
1584 for( const auto& nImageId: anImageIds )
1585 {
1586 poXObjects->Add(CPLOPrintf("Image%d", nImageId.toInt()), nImageId, 0);
1587 }
1588 GDALPDFDictionaryRW* poResources = new GDALPDFDictionaryRW();
1589 poResources->Add("XObject", poXObjects);
1590
1591 oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
1592 .Add("BBox", &((new GDALPDFArrayRW())
1593 ->Add(0).Add(0)).
1594 Add(oPageContext.m_dfWidthInUserUnit).
1595 Add(oPageContext.m_dfHeightInUserUnit))
1596 .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
1597 .Add("Group", poGroup)
1598 .Add("Resources", poResources);
1599
1600
1601 StartObjWithStream(nGroupId, oDictGroup,
1602 oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
1603 VSIFPrintfL(m_fp, "%s", osGroupStream.c_str());
1604 EndObjWithStream();
1605
1606 oPageContext.m_oXObjects[
1607 CPLOPrintf("Group%d", nGroupId.toInt())] = nGroupId;
1608 oPageContext.m_osDrawingStream +=
1609 CPLOPrintf("/Group%d Do\n", nGroupId.toInt());
1610 }
1611
1612 EndBlending(psNode, oPageContext);
1613
1614 return true;
1615 }
1616
1617 /************************************************************************/
1618 /* SetupVectorGeoreferencing() */
1619 /************************************************************************/
1620
SetupVectorGeoreferencing(const char * pszGeoreferencingId,OGRLayer * poLayer,const PageContext & oPageContext,double & dfClippingMinX,double & dfClippingMinY,double & dfClippingMaxX,double & dfClippingMaxY,double adfMatrix[4],std::unique_ptr<OGRCoordinateTransformation> & poCT)1621 bool GDALPDFComposerWriter::SetupVectorGeoreferencing(
1622 const char* pszGeoreferencingId,
1623 OGRLayer* poLayer,
1624 const PageContext& oPageContext,
1625 double& dfClippingMinX,
1626 double& dfClippingMinY,
1627 double& dfClippingMaxX,
1628 double& dfClippingMaxY,
1629 double adfMatrix[4],
1630 std::unique_ptr<OGRCoordinateTransformation>& poCT)
1631 {
1632 CPLAssert( pszGeoreferencingId );
1633
1634 auto iter = oPageContext.m_oMapGeoreferencedId.find(pszGeoreferencingId);
1635 if( iter == oPageContext.m_oMapGeoreferencedId.end() )
1636 {
1637 CPLError(CE_Failure, CPLE_AppDefined,
1638 "Cannot find georeferencing of id %s",
1639 pszGeoreferencingId);
1640 return false;
1641 }
1642 auto& georeferencing = iter->second;
1643 const double dfX1 = georeferencing.m_bboxX1;
1644 const double dfY1 = georeferencing.m_bboxY1;
1645 const double dfX2 = georeferencing.m_bboxX2;
1646 const double dfY2 = georeferencing.m_bboxY2;
1647
1648 dfClippingMinX = APPLY_GT_X(georeferencing.m_adfGT, dfX1, dfY1);
1649 dfClippingMinY = APPLY_GT_Y(georeferencing.m_adfGT, dfX1, dfY1);
1650 dfClippingMaxX = APPLY_GT_X(georeferencing.m_adfGT, dfX2, dfY2);
1651 dfClippingMaxY = APPLY_GT_Y(georeferencing.m_adfGT, dfX2, dfY2);
1652
1653 auto poSRS = poLayer->GetSpatialRef();
1654 if( !poSRS )
1655 {
1656 CPLError(CE_Failure, CPLE_AppDefined, "Layer has no SRS");
1657 return false;
1658 }
1659 if( !poSRS->IsSame(&georeferencing.m_oSRS) )
1660 {
1661 poCT.reset(
1662 OGRCreateCoordinateTransformation(poSRS, &georeferencing.m_oSRS));
1663 }
1664
1665 if( !poCT )
1666 {
1667 poLayer->SetSpatialFilterRect(dfClippingMinX, dfClippingMinY,
1668 dfClippingMaxX, dfClippingMaxY);
1669 }
1670
1671 double adfInvGeoreferencingGT[6]; // from georeferenced to PDF coordinates
1672 CPL_IGNORE_RET_VAL(
1673 GDALInvGeoTransform(const_cast<double*>(georeferencing.m_adfGT),
1674 adfInvGeoreferencingGT));
1675 adfMatrix[0] = adfInvGeoreferencingGT[0];
1676 adfMatrix[1] = adfInvGeoreferencingGT[1];
1677 adfMatrix[2] = adfInvGeoreferencingGT[3];
1678 adfMatrix[3] = adfInvGeoreferencingGT[5];
1679
1680 return true;
1681 }
1682
1683 /************************************************************************/
1684 /* WriteVector() */
1685 /************************************************************************/
1686
WriteVector(const CPLXMLNode * psNode,PageContext & oPageContext)1687 bool GDALPDFComposerWriter::WriteVector(const CPLXMLNode* psNode,
1688 PageContext& oPageContext)
1689 {
1690 const char* pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1691 if( !pszDataset )
1692 {
1693 CPLError(CE_Failure, CPLE_AppDefined,
1694 "Missing dataset");
1695 return false;
1696 }
1697 const char* pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1698 if( !pszLayer )
1699 {
1700 CPLError(CE_Failure, CPLE_AppDefined,
1701 "Missing layer");
1702 return false;
1703 }
1704
1705 GDALDatasetUniquePtr poDS(GDALDataset::Open(
1706 pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
1707 nullptr, nullptr, nullptr));
1708 if( !poDS )
1709 return false;
1710 OGRLayer* poLayer = poDS->GetLayerByName(pszLayer);
1711 if( !poLayer )
1712 {
1713 CPLError(CE_Failure, CPLE_AppDefined,
1714 "Cannt find layer %s", pszLayer);
1715 return false;
1716 }
1717 const bool bVisible =
1718 CPLTestBool(CPLGetXMLValue(psNode, "visible", "true"));
1719
1720 const auto psLogicalStructure =
1721 CPLGetXMLNode(psNode, "LogicalStructure");
1722 const char* pszOGRDisplayField = nullptr;
1723 std::vector<CPLString> aosIncludedFields;
1724 const bool bLogicalStructure = psLogicalStructure != nullptr;
1725 if( psLogicalStructure )
1726 {
1727 pszOGRDisplayField = CPLGetXMLValue(psLogicalStructure,
1728 "fieldToDisplay", nullptr);
1729 if( CPLGetXMLNode(psLogicalStructure, "ExcludeAllFields") != nullptr ||
1730 CPLGetXMLNode(psLogicalStructure, "IncludeField") != nullptr )
1731 {
1732 for(const auto* psIter = psLogicalStructure->psChild;
1733 psIter; psIter = psIter->psNext)
1734 {
1735 if( psIter->eType == CXT_Element &&
1736 strcmp(psIter->pszValue, "IncludeField") == 0 )
1737 {
1738 aosIncludedFields.push_back(CPLGetXMLValue(psIter, nullptr, ""));
1739 }
1740 }
1741 }
1742 else
1743 {
1744 std::set<CPLString> oSetExcludedFields;
1745 for(const auto* psIter = psLogicalStructure->psChild;
1746 psIter; psIter = psIter->psNext)
1747 {
1748 if( psIter->eType == CXT_Element &&
1749 strcmp(psIter->pszValue, "ExcludeField") == 0 )
1750 {
1751 oSetExcludedFields.insert(CPLGetXMLValue(psIter, nullptr, ""));
1752 }
1753 }
1754 const auto poLayerDefn = poLayer->GetLayerDefn();
1755 for( int i = 0; i < poLayerDefn->GetFieldCount(); i++ )
1756 {
1757 const auto poFieldDefn = poLayerDefn->GetFieldDefn(i);
1758 const char* pszName = poFieldDefn->GetNameRef();
1759 if( oSetExcludedFields.find(pszName) == oSetExcludedFields.end() )
1760 {
1761 aosIncludedFields.push_back(pszName);
1762 }
1763 }
1764 }
1765 }
1766 const char* pszStyleString = CPLGetXMLValue(psNode,
1767 "ogrStyleString", nullptr);
1768 const char* pszOGRLinkField = CPLGetXMLValue(psNode,
1769 "linkAttribute", nullptr);
1770
1771 const char* pszGeoreferencingId =
1772 CPLGetXMLValue(psNode, "georeferencingId", nullptr);
1773 std::unique_ptr<OGRCoordinateTransformation> poCT;
1774 double dfClippingMinX = 0;
1775 double dfClippingMinY = 0;
1776 double dfClippingMaxX = 0;
1777 double dfClippingMaxY = 0;
1778 double adfMatrix[4] = { 0, 1, 0, 1 };
1779 if( pszGeoreferencingId &&
1780 !SetupVectorGeoreferencing(pszGeoreferencingId,
1781 poLayer, oPageContext,
1782 dfClippingMinX, dfClippingMinY,
1783 dfClippingMaxX, dfClippingMaxY,
1784 adfMatrix, poCT) )
1785 {
1786 return false;
1787 }
1788
1789 double dfOpacityFactor = 1.0;
1790 if( !bVisible )
1791 {
1792 if( oPageContext.m_oExtGState.find("GSinvisible") ==
1793 oPageContext.m_oExtGState.end() )
1794 {
1795 auto nExtGState = AllocNewObject();
1796 StartObj(nExtGState);
1797 {
1798 GDALPDFDictionaryRW gs;
1799 gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1800 gs.Add("ca", 0);
1801 gs.Add("CA", 0);
1802 VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1803 }
1804 EndObj();
1805 oPageContext.m_oExtGState["GSinvisible"] = nExtGState;
1806 }
1807 oPageContext.m_osDrawingStream += "q\n";
1808 oPageContext.m_osDrawingStream += "/GSinvisible gs\n";
1809 oPageContext.m_osDrawingStream += "0 w\n";
1810 dfOpacityFactor = 0;
1811 }
1812 else
1813 {
1814 StartBlending(psNode, oPageContext, dfOpacityFactor);
1815 }
1816
1817 if (!m_nStructTreeRootId.toBool())
1818 m_nStructTreeRootId = AllocNewObject();
1819
1820 GDALPDFObjectNum nFeatureLayerId;
1821 if( bLogicalStructure )
1822 {
1823 nFeatureLayerId = AllocNewObject();
1824 m_anFeatureLayerId.push_back(nFeatureLayerId);
1825 }
1826
1827 std::vector<GDALPDFObjectNum> anFeatureUserProperties;
1828 for( auto&& poFeature: poLayer )
1829 {
1830 auto hFeat = OGRFeature::ToHandle(poFeature.get());
1831 auto hGeom = OGR_F_GetGeometryRef(hFeat);
1832 if( !hGeom || OGR_G_IsEmpty(hGeom) )
1833 continue;
1834 if( poCT )
1835 {
1836 if( OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) != OGRERR_NONE )
1837 continue;
1838
1839 OGREnvelope sEnvelope;
1840 OGR_G_GetEnvelope(hGeom, &sEnvelope);
1841 if( sEnvelope.MinX > dfClippingMaxX ||
1842 sEnvelope.MaxX < dfClippingMinX ||
1843 sEnvelope.MinY > dfClippingMaxY ||
1844 sEnvelope.MaxY < dfClippingMinY )
1845 {
1846 continue;
1847 }
1848 }
1849
1850 if( bLogicalStructure )
1851 {
1852 CPLString osOutFeatureName;
1853 anFeatureUserProperties.push_back(
1854 WriteAttributes(
1855 hFeat,
1856 aosIncludedFields,
1857 pszOGRDisplayField,
1858 oPageContext.m_nMCID,
1859 nFeatureLayerId,
1860 m_asPageId.back(),
1861 osOutFeatureName));
1862 }
1863
1864 ObjectStyle os;
1865 GetObjectStyle(pszStyleString, hFeat, adfMatrix,
1866 m_oMapSymbolFilenameToDesc, os);
1867 os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
1868 os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
1869
1870 const double dfRadius = os.dfSymbolSize;
1871
1872 if( os.nImageSymbolId.toBool() )
1873 {
1874 oPageContext.m_oXObjects[
1875 CPLOPrintf("SymImage%d", os.nImageSymbolId.toInt())] = os.nImageSymbolId;
1876 }
1877
1878 if( pszOGRLinkField )
1879 {
1880 OGREnvelope sEnvelope;
1881 OGR_G_GetEnvelope(hGeom, &sEnvelope);
1882 int bboxXMin, bboxYMin, bboxXMax, bboxYMax;
1883 ComputeIntBBox(hGeom, sEnvelope, adfMatrix, os, dfRadius,
1884 bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1885
1886 auto nLinkId = WriteLink(hFeat, pszOGRLinkField, adfMatrix,
1887 bboxXMin, bboxYMin, bboxXMax, bboxYMax);
1888 if( nLinkId.toBool() )
1889 oPageContext.m_anAnnotationsId.push_back(nLinkId);
1890 }
1891
1892 if( bLogicalStructure )
1893 {
1894 oPageContext.m_osDrawingStream +=
1895 CPLOPrintf("/feature <</MCID %d>> BDC\n", oPageContext.m_nMCID);
1896 }
1897
1898 if( bVisible || bLogicalStructure )
1899 {
1900 oPageContext.m_osDrawingStream += "q\n";
1901 if (bVisible && (os.nPenA != 255 || os.nBrushA != 255))
1902 {
1903 CPLString osGSName;
1904 osGSName.Printf("GS_CA_%d_ca_%d", os.nPenA, os.nBrushA);
1905 if( oPageContext.m_oExtGState.find(osGSName) ==
1906 oPageContext.m_oExtGState.end() )
1907 {
1908 auto nExtGState = AllocNewObject();
1909 StartObj(nExtGState);
1910 {
1911 GDALPDFDictionaryRW gs;
1912 gs.Add("Type", GDALPDFObjectRW::CreateName("ExtGState"));
1913 if (os.nPenA != 255)
1914 gs.Add("CA", (os.nPenA == 127 || os.nPenA == 128) ? 0.5 : os.nPenA / 255.0);
1915 if (os.nBrushA != 255)
1916 gs.Add("ca", (os.nBrushA == 127 || os.nBrushA == 128) ? 0.5 : os.nBrushA / 255.0 );
1917 VSIFPrintfL(m_fp, "%s\n", gs.Serialize().c_str());
1918 }
1919 EndObj();
1920 oPageContext.m_oExtGState[osGSName] = nExtGState;
1921 }
1922 oPageContext.m_osDrawingStream += "/" + osGSName +" gs\n";
1923 }
1924
1925 oPageContext.m_osDrawingStream +=
1926 GenerateDrawingStream(hGeom, adfMatrix, os, dfRadius);
1927
1928 oPageContext.m_osDrawingStream += "Q\n";
1929 }
1930
1931 if( bLogicalStructure )
1932 {
1933 oPageContext.m_osDrawingStream += "EMC\n";
1934 oPageContext.m_nMCID ++;
1935 }
1936 }
1937
1938 if( bLogicalStructure )
1939 {
1940 for( const auto& num: anFeatureUserProperties )
1941 {
1942 oPageContext.m_anFeatureUserProperties.push_back(num);
1943 }
1944
1945 {
1946 StartObj(nFeatureLayerId);
1947
1948 GDALPDFDictionaryRW oDict;
1949 GDALPDFDictionaryRW* poDictA = new GDALPDFDictionaryRW();
1950 oDict.Add("A", poDictA);
1951 poDictA->Add("O", GDALPDFObjectRW::CreateName("UserProperties"));
1952 GDALPDFArrayRW* poArrayK = new GDALPDFArrayRW();
1953 for( const auto& num: anFeatureUserProperties )
1954 poArrayK->Add(num, 0);
1955 oDict.Add("K", poArrayK);
1956 oDict.Add("P", m_nStructTreeRootId, 0);
1957 oDict.Add("S", GDALPDFObjectRW::CreateName("Layer"));
1958
1959 const char* pszOGRDisplayName =
1960 CPLGetXMLValue(psLogicalStructure, "displayLayerName", poLayer->GetName());
1961 oDict.Add("T", pszOGRDisplayName);
1962
1963 VSIFPrintfL(m_fp, "%s\n", oDict.Serialize().c_str());
1964
1965 EndObj();
1966 }
1967 }
1968
1969 if( !bVisible )
1970 {
1971 oPageContext.m_osDrawingStream += "Q\n";
1972 }
1973 else
1974 {
1975 EndBlending(psNode, oPageContext);
1976 }
1977
1978 return true;
1979 }
1980
1981 /************************************************************************/
1982 /* WriteVectorLabel() */
1983 /************************************************************************/
1984
WriteVectorLabel(const CPLXMLNode * psNode,PageContext & oPageContext)1985 bool GDALPDFComposerWriter::WriteVectorLabel(const CPLXMLNode* psNode,
1986 PageContext& oPageContext)
1987 {
1988 const char* pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
1989 if( !pszDataset )
1990 {
1991 CPLError(CE_Failure, CPLE_AppDefined,
1992 "Missing dataset");
1993 return false;
1994 }
1995 const char* pszLayer = CPLGetXMLValue(psNode, "layer", nullptr);
1996 if( !pszLayer )
1997 {
1998 CPLError(CE_Failure, CPLE_AppDefined,
1999 "Missing layer");
2000 return false;
2001 }
2002
2003 GDALDatasetUniquePtr poDS(GDALDataset::Open(
2004 pszDataset, GDAL_OF_VECTOR | GDAL_OF_VERBOSE_ERROR,
2005 nullptr, nullptr, nullptr));
2006 if( !poDS )
2007 return false;
2008 OGRLayer* poLayer = poDS->GetLayerByName(pszLayer);
2009 if( !poLayer )
2010 {
2011 CPLError(CE_Failure, CPLE_AppDefined,
2012 "Cannt find layer %s", pszLayer);
2013 return false;
2014 }
2015
2016 const char* pszStyleString = CPLGetXMLValue(psNode,
2017 "ogrStyleString", nullptr);
2018
2019 double dfOpacityFactor = 1.0;
2020 StartBlending(psNode, oPageContext, dfOpacityFactor);
2021
2022 const char* pszGeoreferencingId =
2023 CPLGetXMLValue(psNode, "georeferencingId", nullptr);
2024 std::unique_ptr<OGRCoordinateTransformation> poCT;
2025 double dfClippingMinX = 0;
2026 double dfClippingMinY = 0;
2027 double dfClippingMaxX = 0;
2028 double dfClippingMaxY = 0;
2029 double adfMatrix[4] = { 0, 1, 0, 1 };
2030 if( pszGeoreferencingId &&
2031 !SetupVectorGeoreferencing(pszGeoreferencingId,
2032 poLayer, oPageContext,
2033 dfClippingMinX, dfClippingMinY,
2034 dfClippingMaxX, dfClippingMaxY,
2035 adfMatrix, poCT) )
2036 {
2037 return false;
2038 }
2039
2040 for( auto&& poFeature: poLayer )
2041 {
2042 auto hFeat = OGRFeature::ToHandle(poFeature.get());
2043 auto hGeom = OGR_F_GetGeometryRef(hFeat);
2044 if( !hGeom || OGR_G_IsEmpty(hGeom) )
2045 continue;
2046 if( poCT )
2047 {
2048 if( OGRGeometry::FromHandle(hGeom)->transform(poCT.get()) != OGRERR_NONE )
2049 continue;
2050
2051 OGREnvelope sEnvelope;
2052 OGR_G_GetEnvelope(hGeom, &sEnvelope);
2053 if( sEnvelope.MinX > dfClippingMaxX ||
2054 sEnvelope.MaxX < dfClippingMinX ||
2055 sEnvelope.MinY > dfClippingMaxY ||
2056 sEnvelope.MaxY < dfClippingMinY )
2057 {
2058 continue;
2059 }
2060 }
2061
2062 ObjectStyle os;
2063 GetObjectStyle(pszStyleString, hFeat, adfMatrix,
2064 m_oMapSymbolFilenameToDesc, os);
2065 os.nPenA = static_cast<int>(std::round(os.nPenA * dfOpacityFactor));
2066 os.nBrushA = static_cast<int>(std::round(os.nBrushA * dfOpacityFactor));
2067
2068 if (!os.osLabelText.empty() &&
2069 wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPoint)
2070 {
2071 auto nObjectId = WriteLabel(hGeom, adfMatrix, os,
2072 oPageContext.m_eStreamCompressMethod,
2073 0,0,
2074 oPageContext.m_dfWidthInUserUnit,
2075 oPageContext.m_dfHeightInUserUnit);
2076 oPageContext.m_osDrawingStream +=
2077 CPLOPrintf("/Label%d Do\n", nObjectId.toInt());
2078 oPageContext.m_oXObjects[
2079 CPLOPrintf("Label%d", nObjectId.toInt())] = nObjectId;
2080 }
2081 }
2082
2083 EndBlending(psNode, oPageContext);
2084
2085 return true;
2086 }
2087
2088 #ifdef HAVE_PDF_READ_SUPPORT
2089
2090 /************************************************************************/
2091 /* EmitNewObject() */
2092 /************************************************************************/
2093
EmitNewObject(GDALPDFObject * poObj,RemapType & oRemapObjectRefs)2094 GDALPDFObjectNum GDALPDFComposerWriter::EmitNewObject(GDALPDFObject* poObj,
2095 RemapType& oRemapObjectRefs)
2096 {
2097 auto nId = AllocNewObject();
2098 const auto nRefNum = poObj->GetRefNum();
2099 if( nRefNum.toBool() )
2100 {
2101 int nRefGen = poObj->GetRefGen();
2102 std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2103 oRemapObjectRefs[oKey] = nId;
2104 }
2105 CPLString osStr;
2106 if( !SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs) )
2107 return GDALPDFObjectNum();
2108 StartObj(nId);
2109 VSIFWriteL(osStr.data(), 1, osStr.size(), m_fp);
2110 VSIFPrintfL(m_fp, "\n");
2111 EndObj();
2112 return nId;
2113 }
2114
2115 /************************************************************************/
2116 /* SerializeAndRenumber() */
2117 /************************************************************************/
2118
SerializeAndRenumber(CPLString & osStr,GDALPDFObject * poObj,RemapType & oRemapObjectRefs)2119 bool GDALPDFComposerWriter::SerializeAndRenumber(CPLString& osStr,
2120 GDALPDFObject* poObj,
2121 RemapType& oRemapObjectRefs)
2122 {
2123 auto nRefNum = poObj->GetRefNum();
2124 if( nRefNum.toBool() )
2125 {
2126 int nRefGen = poObj->GetRefGen();
2127
2128 std::pair<int, int> oKey(nRefNum.toInt(), nRefGen);
2129 auto oIter = oRemapObjectRefs.find(oKey);
2130 if( oIter != oRemapObjectRefs.end() )
2131 {
2132 osStr.append(CPLSPrintf("%d 0 R", oIter->second.toInt()));
2133 return true;
2134 }
2135 else
2136 {
2137 auto nId = EmitNewObject(poObj, oRemapObjectRefs);
2138 osStr.append(CPLSPrintf("%d 0 R", nId.toInt()));
2139 return nId.toBool();
2140 }
2141 }
2142 else
2143 {
2144 return SerializeAndRenumberIgnoreRef(osStr, poObj, oRemapObjectRefs);
2145 }
2146 }
2147
2148 /************************************************************************/
2149 /* SerializeAndRenumberIgnoreRef() */
2150 /************************************************************************/
2151
SerializeAndRenumberIgnoreRef(CPLString & osStr,GDALPDFObject * poObj,RemapType & oRemapObjectRefs)2152 bool GDALPDFComposerWriter::SerializeAndRenumberIgnoreRef(CPLString& osStr,
2153 GDALPDFObject* poObj,
2154 RemapType& oRemapObjectRefs)
2155 {
2156 switch(poObj->GetType())
2157 {
2158 case PDFObjectType_Array:
2159 {
2160 auto poArray = poObj->GetArray();
2161 int nLength = poArray->GetLength();
2162 osStr.append("[ ");
2163 for(int i=0;i<nLength;i++)
2164 {
2165 if( !SerializeAndRenumber(osStr, poArray->Get(i), oRemapObjectRefs) )
2166 return false;
2167 osStr.append(" ");
2168 }
2169 osStr.append("]");
2170 break;
2171 }
2172 case PDFObjectType_Dictionary:
2173 {
2174 osStr.append("<< ");
2175 auto poDict = poObj->GetDictionary();
2176 auto& oMap = poDict->GetValues();
2177 for( const auto& oIter: oMap )
2178 {
2179 const char* pszKey = oIter.first.c_str();
2180 GDALPDFObject* poSubObj = oIter.second;
2181 osStr.append("/");
2182 osStr.append(pszKey);
2183 osStr.append(" ");
2184 if( !SerializeAndRenumber(osStr, poSubObj, oRemapObjectRefs) )
2185 return false;
2186 osStr.append(" ");
2187 }
2188 osStr.append(">>");
2189 auto poStream = poObj->GetStream();
2190 if( poStream )
2191 {
2192 // CPLAssert( poObj->GetRefNum().toBool() ); // should be a top level object
2193 osStr.append("\nstream\n");
2194 auto pRawBytes = poStream->GetRawBytes();
2195 if( !pRawBytes )
2196 {
2197 CPLError(CE_Failure, CPLE_AppDefined,
2198 "Cannot get stream content");
2199 return false;
2200 }
2201 osStr.append(pRawBytes, poStream->GetRawLength());
2202 VSIFree(pRawBytes);
2203 osStr.append("\nendstream\n");
2204 }
2205 break;
2206 }
2207 case PDFObjectType_Unknown:
2208 {
2209 CPLError(CE_Failure, CPLE_AppDefined,
2210 "Corrupted PDF");
2211 return false;
2212 }
2213 default:
2214 {
2215 poObj->Serialize(osStr, false);
2216 break;
2217 }
2218 }
2219 return true;
2220 }
2221
2222 /************************************************************************/
2223 /* SerializeAndRenumber() */
2224 /************************************************************************/
2225
SerializeAndRenumber(GDALPDFObject * poObj)2226 GDALPDFObjectNum GDALPDFComposerWriter::SerializeAndRenumber(GDALPDFObject* poObj)
2227 {
2228 RemapType oRemapObjectRefs;
2229 return EmitNewObject(poObj, oRemapObjectRefs);
2230 }
2231
2232 /************************************************************************/
2233 /* WritePDF() */
2234 /************************************************************************/
2235
WritePDF(const CPLXMLNode * psNode,PageContext & oPageContext)2236 bool GDALPDFComposerWriter::WritePDF(const CPLXMLNode* psNode,
2237 PageContext& oPageContext)
2238 {
2239 const char* pszDataset = CPLGetXMLValue(psNode, "dataset", nullptr);
2240 if( !pszDataset )
2241 {
2242 CPLError(CE_Failure, CPLE_AppDefined,
2243 "Missing dataset");
2244 return false;
2245 }
2246
2247 GDALOpenInfo oOpenInfo(pszDataset, GA_ReadOnly);
2248 std::unique_ptr<PDFDataset> poDS(PDFDataset::Open(&oOpenInfo));
2249 if( !poDS )
2250 {
2251 CPLError(CE_Failure, CPLE_OpenFailed,
2252 "%s is not a valid PDF file", pszDataset);
2253 return false;
2254 }
2255 if( poDS->GetPageWidth() != oPageContext.m_dfWidthInUserUnit ||
2256 poDS->GetPageHeight() != oPageContext.m_dfHeightInUserUnit )
2257 {
2258 CPLError(CE_Warning, CPLE_AppDefined,
2259 "Dimensions of the inserted PDF page are %fx%f, which is "
2260 "different from the output PDF page %fx%f",
2261 poDS->GetPageWidth(),
2262 poDS->GetPageHeight(),
2263 oPageContext.m_dfWidthInUserUnit,
2264 oPageContext.m_dfHeightInUserUnit);
2265 }
2266 auto poPageObj = poDS->GetPageObj();
2267 if( !poPageObj )
2268 return false;
2269 auto poPageDict = poPageObj->GetDictionary();
2270 if( !poPageDict )
2271 return false;
2272 auto poContents = poPageDict->Get("Contents");
2273 if (poContents != nullptr && poContents->GetType() == PDFObjectType_Array)
2274 {
2275 GDALPDFArray* poContentsArray = poContents->GetArray();
2276 if (poContentsArray->GetLength() == 1)
2277 {
2278 poContents = poContentsArray->Get(0);
2279 }
2280 }
2281 if (poContents == nullptr ||
2282 poContents->GetType() != PDFObjectType_Dictionary )
2283 {
2284 CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents");
2285 return false;
2286 }
2287
2288 auto poResources = poPageDict->Get("Resources");
2289 if( !poResources )
2290 {
2291 CPLError(CE_Failure, CPLE_AppDefined, "Missing Resources");
2292 return false;
2293 }
2294
2295 // Serialize and renumber the Page Resources dictionary
2296 auto nClonedResources = SerializeAndRenumber(poResources);
2297 if( !nClonedResources.toBool() )
2298 {
2299 return false;
2300 }
2301
2302 // Create a Transparency group using cloned Page Resources, and
2303 // the Page Contents stream
2304 auto nFormId = AllocNewObject();
2305 GDALPDFDictionaryRW oDictGroup;
2306 GDALPDFDictionaryRW* poGroup = new GDALPDFDictionaryRW();
2307 poGroup->Add("Type", GDALPDFObjectRW::CreateName("Group"))
2308 .Add("S",GDALPDFObjectRW::CreateName("Transparency"));
2309
2310 oDictGroup.Add("Type", GDALPDFObjectRW::CreateName("XObject"))
2311 .Add("BBox", &((new GDALPDFArrayRW())
2312 ->Add(0).Add(0)).
2313 Add(oPageContext.m_dfWidthInUserUnit).
2314 Add(oPageContext.m_dfHeightInUserUnit))
2315 .Add("Subtype", GDALPDFObjectRW::CreateName("Form"))
2316 .Add("Group", poGroup)
2317 .Add("Resources", nClonedResources, 0);
2318
2319 auto poStream = poContents->GetStream();
2320 if( !poStream )
2321 {
2322 CPLError(CE_Failure, CPLE_AppDefined, "Missing Contents stream");
2323 return false;
2324 }
2325 auto pabyContents = poStream->GetBytes();
2326 if( !pabyContents )
2327 {
2328 return false;
2329 }
2330 const auto nContentsLength = poStream->GetLength();
2331
2332 StartObjWithStream(nFormId, oDictGroup,
2333 oPageContext.m_eStreamCompressMethod != COMPRESS_NONE);
2334 VSIFWriteL(pabyContents, 1, nContentsLength, m_fp);
2335 VSIFree(pabyContents);
2336 EndObjWithStream();
2337
2338 // Paint the transparency group
2339 double dfIgnoredOpacity;
2340 StartBlending(psNode, oPageContext, dfIgnoredOpacity);
2341
2342 oPageContext.m_osDrawingStream +=
2343 CPLOPrintf("/Form%d Do\n", nFormId.toInt());
2344 oPageContext.m_oXObjects[
2345 CPLOPrintf("Form%d", nFormId.toInt())] = nFormId;
2346
2347 EndBlending(psNode, oPageContext);
2348
2349 return true;
2350 }
2351
2352 #endif // HAVE_PDF_READ_SUPPORT
2353
2354 /************************************************************************/
2355 /* Generate() */
2356 /************************************************************************/
2357
Generate(const CPLXMLNode * psComposition)2358 bool GDALPDFComposerWriter::Generate(const CPLXMLNode* psComposition)
2359 {
2360 m_osJPEG2000Driver = CPLGetXMLValue(psComposition, "JPEG2000Driver", "");
2361
2362 auto psMetadata = CPLGetXMLNode(psComposition, "Metadata");
2363 if( psMetadata )
2364 {
2365 SetInfo(
2366 CPLGetXMLValue(psMetadata, "Author", nullptr),
2367 CPLGetXMLValue(psMetadata, "Producer", nullptr),
2368 CPLGetXMLValue(psMetadata, "Creator", nullptr),
2369 CPLGetXMLValue(psMetadata, "CreationDate", nullptr),
2370 CPLGetXMLValue(psMetadata, "Subject", nullptr),
2371 CPLGetXMLValue(psMetadata, "Title", nullptr),
2372 CPLGetXMLValue(psMetadata, "Keywords", nullptr));
2373 SetXMP(nullptr, CPLGetXMLValue(psMetadata, "XMP", nullptr));
2374 }
2375
2376 const char* pszJavascript = CPLGetXMLValue(psComposition, "Javascript", nullptr);
2377 if( pszJavascript )
2378 WriteJavascript(pszJavascript, false);
2379
2380 auto psLayerTree = CPLGetXMLNode(psComposition, "LayerTree");
2381 if( psLayerTree )
2382 {
2383 m_bDisplayLayersOnlyOnVisiblePages = CPLTestBool(
2384 CPLGetXMLValue(psLayerTree, "displayOnlyOnVisiblePages", "false"));
2385 if( !CreateLayerTree(psLayerTree, GDALPDFObjectNum(), &m_oTreeOfOGC) )
2386 return false;
2387 }
2388
2389 bool bFoundPage = false;
2390 for(const auto* psIter = psComposition->psChild; psIter; psIter = psIter->psNext)
2391 {
2392 if( psIter->eType == CXT_Element &&
2393 strcmp(psIter->pszValue, "Page") == 0 )
2394 {
2395 if( !GeneratePage(psIter) )
2396 return false;
2397 bFoundPage = true;
2398 }
2399 }
2400 if( !bFoundPage )
2401 {
2402 CPLError(CE_Failure, CPLE_AppDefined,
2403 "At least one page should be defined");
2404 return false;
2405 }
2406
2407 auto psOutline = CPLGetXMLNode(psComposition, "Outline");
2408 if( psOutline )
2409 {
2410 if( !CreateOutline(psOutline) )
2411 return false;
2412 }
2413
2414 return true;
2415 }
2416
2417
2418 /************************************************************************/
2419 /* GDALPDFErrorHandler() */
2420 /************************************************************************/
2421
GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,CPL_UNUSED CPLErrorNum nType,const char * pszMsg)2422 static void CPL_STDCALL GDALPDFErrorHandler(CPL_UNUSED CPLErr eErr,
2423 CPL_UNUSED CPLErrorNum nType,
2424 const char *pszMsg)
2425 {
2426 std::vector<CPLString> *paosErrors =
2427 static_cast<std::vector<CPLString> *>(CPLGetErrorHandlerUserData());
2428 paosErrors->push_back(pszMsg);
2429 }
2430
2431 /************************************************************************/
2432 /* GDALPDFCreateFromCompositionFile() */
2433 /************************************************************************/
2434
GDALPDFCreateFromCompositionFile(const char * pszPDFFilename,const char * pszXMLFilename)2435 GDALDataset* GDALPDFCreateFromCompositionFile(const char* pszPDFFilename,
2436 const char *pszXMLFilename)
2437 {
2438 CPLXMLTreeCloser oXML(
2439 (pszXMLFilename[0] == '<' &&
2440 strstr(pszXMLFilename, "<PDFComposition") != nullptr) ?
2441 CPLParseXMLString(pszXMLFilename) : CPLParseXMLFile(pszXMLFilename));
2442 if( !oXML.get() )
2443 return nullptr;
2444 auto psComposition = CPLGetXMLNode(oXML.get(), "=PDFComposition");
2445 if( !psComposition )
2446 {
2447 CPLError(CE_Failure, CPLE_AppDefined, "Cannot find PDFComposition");
2448 return nullptr;
2449 }
2450
2451 // XML Validation.
2452 if( CPLTestBool(CPLGetConfigOption("GDAL_XML_VALIDATION", "YES")) )
2453 {
2454 const char *pszXSD = CPLFindFile("gdal", "pdfcomposition.xsd");
2455 if( pszXSD != nullptr )
2456 {
2457 std::vector<CPLString> aosErrors;
2458 CPLPushErrorHandlerEx(GDALPDFErrorHandler, &aosErrors);
2459 const int bRet = CPLValidateXML(pszXMLFilename, pszXSD, nullptr);
2460 CPLPopErrorHandler();
2461 if( !bRet )
2462 {
2463 if( !aosErrors.empty() &&
2464 strstr(aosErrors[0].c_str(), "missing libxml2 support") ==
2465 nullptr )
2466 {
2467 for( size_t i = 0; i < aosErrors.size(); i++ )
2468 {
2469 CPLError(CE_Warning, CPLE_AppDefined, "%s",
2470 aosErrors[i].c_str());
2471 }
2472 }
2473 }
2474 CPLErrorReset();
2475 }
2476 }
2477
2478 /* -------------------------------------------------------------------- */
2479 /* Create file. */
2480 /* -------------------------------------------------------------------- */
2481 VSILFILE* fp = VSIFOpenL(pszPDFFilename, "wb");
2482 if( fp == nullptr )
2483 {
2484 CPLError( CE_Failure, CPLE_OpenFailed,
2485 "Unable to create PDF file %s.\n",
2486 pszPDFFilename );
2487 return nullptr;
2488 }
2489
2490 GDALPDFComposerWriter oWriter(fp);
2491 if( !oWriter.Generate(psComposition) )
2492 return nullptr;
2493
2494 return new GDALFakePDFDataset();
2495 }
2496