Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: DXF Translator
4 : * Purpose: Implements OGRDXFWriterDS - the OGRDataSource class used for
5 : * writing a DXF file.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
10 : * Copyright (c) 2010-2012, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 :
17 : #include <cmath>
18 : #include <cstdlib>
19 : #include <limits>
20 :
21 : #include "ogr_dxf.h"
22 : #include "cpl_conv.h"
23 : #include "cpl_string.h"
24 : #include "cpl_vsi_error.h"
25 :
26 : #ifdef EMBED_RESOURCE_FILES
27 : #include "embedded_resources.h"
28 : #endif
29 :
30 : /************************************************************************/
31 : /* OGRDXFWriterDS() */
32 : /************************************************************************/
33 :
34 45 : OGRDXFWriterDS::OGRDXFWriterDS()
35 : : nNextFID(80), poLayer(nullptr), poBlocksLayer(nullptr), fp(nullptr),
36 45 : fpTemp(nullptr), papszLayersToCreate(nullptr), nHANDSEEDOffset(0)
37 : {
38 45 : }
39 :
40 : /************************************************************************/
41 : /* ~OGRDXFWriterDS() */
42 : /************************************************************************/
43 :
44 90 : OGRDXFWriterDS::~OGRDXFWriterDS()
45 :
46 : {
47 45 : if (fp != nullptr)
48 : {
49 : /* --------------------------------------------------------------------
50 : */
51 : /* Transfer over the header into the destination file with any */
52 : /* adjustments or insertions needed. */
53 : /* --------------------------------------------------------------------
54 : */
55 44 : CPLDebug("DXF", "Compose final DXF file from components.");
56 :
57 44 : if (IsMarkedSuppressOnClose() && fpTemp != nullptr)
58 : {
59 1 : CPLDebug("DXF", "Do not copy final DXF when 'suppress on close'.");
60 1 : VSIFCloseL(fpTemp);
61 1 : VSIUnlink(osTempFilename);
62 1 : fpTemp = nullptr;
63 : }
64 :
65 44 : TransferUpdateHeader(fp);
66 :
67 44 : if (fpTemp != nullptr)
68 : {
69 : /* --------------------------------------------------------------------
70 : */
71 : /* Copy in the temporary file contents. */
72 : /* --------------------------------------------------------------------
73 : */
74 43 : VSIFCloseL(fpTemp);
75 43 : fpTemp = VSIFOpenL(osTempFilename, "r");
76 :
77 43 : const char *pszLine = nullptr;
78 2059 : while ((pszLine = CPLReadLineL(fpTemp)) != nullptr)
79 : {
80 2016 : VSIFWriteL(pszLine, 1, strlen(pszLine), fp);
81 2016 : VSIFWriteL("\n", 1, 1, fp);
82 : }
83 :
84 : /* --------------------------------------------------------------------
85 : */
86 : /* Cleanup temporary file. */
87 : /* --------------------------------------------------------------------
88 : */
89 43 : VSIFCloseL(fpTemp);
90 43 : VSIUnlink(osTempFilename);
91 : }
92 :
93 : /* --------------------------------------------------------------------
94 : */
95 : /* Write trailer. */
96 : /* --------------------------------------------------------------------
97 : */
98 44 : if (osTrailerFile != "")
99 44 : TransferUpdateTrailer(fp);
100 :
101 : /* --------------------------------------------------------------------
102 : */
103 : /* Fixup the HANDSEED value now that we know all the entity ids */
104 : /* created. */
105 : /* --------------------------------------------------------------------
106 : */
107 44 : FixupHANDSEED(fp);
108 :
109 : /* --------------------------------------------------------------------
110 : */
111 : /* Close file. */
112 : /* --------------------------------------------------------------------
113 : */
114 :
115 44 : VSIFCloseL(fp);
116 44 : fp = nullptr;
117 : }
118 :
119 : /* -------------------------------------------------------------------- */
120 : /* Destroy layers. */
121 : /* -------------------------------------------------------------------- */
122 45 : delete poLayer;
123 45 : delete poBlocksLayer;
124 :
125 45 : CSLDestroy(papszLayersToCreate);
126 :
127 45 : if (m_bHeaderFileIsTemp)
128 0 : VSIUnlink(osHeaderFile.c_str());
129 45 : if (m_bTrailerFileIsTemp)
130 0 : VSIUnlink(osTrailerFile.c_str());
131 90 : }
132 :
133 : /************************************************************************/
134 : /* TestCapability() */
135 : /************************************************************************/
136 :
137 48 : int OGRDXFWriterDS::TestCapability(const char *pszCap)
138 :
139 : {
140 48 : if (EQUAL(pszCap, ODsCCreateLayer))
141 : // Unable to have more than one OGR entities layer in a DXF file, with
142 : // one options blocks layer.
143 32 : return poBlocksLayer == nullptr || poLayer == nullptr;
144 : else
145 16 : return FALSE;
146 : }
147 :
148 : /************************************************************************/
149 : /* GetLayer() */
150 : /************************************************************************/
151 :
152 0 : OGRLayer *OGRDXFWriterDS::GetLayer(int iLayer)
153 :
154 : {
155 0 : if (iLayer == 0)
156 0 : return poLayer;
157 : else
158 0 : return nullptr;
159 : }
160 :
161 : /************************************************************************/
162 : /* GetLayerCount() */
163 : /************************************************************************/
164 :
165 0 : int OGRDXFWriterDS::GetLayerCount()
166 :
167 : {
168 0 : if (poLayer)
169 0 : return 1;
170 : else
171 0 : return 0;
172 : }
173 :
174 : /************************************************************************/
175 : /* Open() */
176 : /************************************************************************/
177 :
178 45 : int OGRDXFWriterDS::Open(const char *pszFilename, char **papszOptions)
179 :
180 : {
181 : /* -------------------------------------------------------------------- */
182 : /* Open the standard header, or a user provided header. */
183 : /* -------------------------------------------------------------------- */
184 45 : if (CSLFetchNameValue(papszOptions, "HEADER") != nullptr)
185 3 : osHeaderFile = CSLFetchNameValue(papszOptions, "HEADER");
186 : else
187 : {
188 : #ifdef EMBED_RESOURCE_FILES
189 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
190 : #endif
191 : #ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
192 : const char *pszValue = nullptr;
193 : #else
194 42 : const char *pszValue = CPLFindFile("gdal", "header.dxf");
195 42 : if (pszValue == nullptr)
196 : #endif
197 : {
198 : #ifdef EMBED_RESOURCE_FILES
199 : static const bool bOnce [[maybe_unused]] = []()
200 : {
201 : CPLDebug("DXF", "Using embedded header.dxf");
202 : return true;
203 : }();
204 : pszValue = VSIMemGenerateHiddenFilename("header.dxf");
205 : VSIFCloseL(VSIFileFromMemBuffer(
206 : pszValue,
207 : const_cast<GByte *>(
208 : reinterpret_cast<const GByte *>(OGRDXFGetHEADER())),
209 : static_cast<int>(strlen(OGRDXFGetHEADER())),
210 : /* bTakeOwnership = */ false));
211 : m_bHeaderFileIsTemp = true;
212 : #else
213 0 : CPLError(CE_Failure, CPLE_OpenFailed,
214 : "Failed to find template header file header.dxf for "
215 : "reading,\nis GDAL_DATA set properly?");
216 0 : return FALSE;
217 : #endif
218 : }
219 42 : osHeaderFile = pszValue;
220 : }
221 :
222 : /* -------------------------------------------------------------------- */
223 : /* Establish the name for our trailer file. */
224 : /* -------------------------------------------------------------------- */
225 45 : if (CSLFetchNameValue(papszOptions, "TRAILER") != nullptr)
226 0 : osTrailerFile = CSLFetchNameValue(papszOptions, "TRAILER");
227 : else
228 : {
229 : #ifdef EMBED_RESOURCE_FILES
230 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
231 : #endif
232 : #ifdef USE_ONLY_EMBEDDED_RESOURCE_FILES
233 : const char *pszValue = nullptr;
234 : #else
235 45 : const char *pszValue = CPLFindFile("gdal", "trailer.dxf");
236 45 : if (pszValue != nullptr)
237 45 : osTrailerFile = pszValue;
238 : #endif
239 : #ifdef EMBED_RESOURCE_FILES
240 : if (!pszValue)
241 : {
242 : static const bool bOnce [[maybe_unused]] = []()
243 : {
244 : CPLDebug("DXF", "Using embedded trailer.dxf");
245 : return true;
246 : }();
247 : osTrailerFile = VSIMemGenerateHiddenFilename("trailer.dxf");
248 : m_bTrailerFileIsTemp = false;
249 : VSIFCloseL(VSIFileFromMemBuffer(
250 : osTrailerFile.c_str(),
251 : const_cast<GByte *>(
252 : reinterpret_cast<const GByte *>(OGRDXFGetTRAILER())),
253 : static_cast<int>(strlen(OGRDXFGetTRAILER())),
254 : /* bTakeOwnership = */ false));
255 : }
256 : #endif
257 : }
258 :
259 : /* -------------------------------------------------------------------- */
260 : /* What entity id do we want to start with when writing? Small */
261 : /* values run a risk of overlapping some undetected entity in */
262 : /* the header or trailer despite the prescanning we do. */
263 : /* -------------------------------------------------------------------- */
264 : #ifdef DEBUG
265 45 : nNextFID = 80;
266 : #else
267 : nNextFID = 131072;
268 : #endif
269 :
270 45 : if (CSLFetchNameValue(papszOptions, "FIRST_ENTITY") != nullptr)
271 1 : nNextFID = atoi(CSLFetchNameValue(papszOptions, "FIRST_ENTITY"));
272 :
273 : /* -------------------------------------------------------------------- */
274 : /* Prescan the header and trailer for entity codes. */
275 : /* -------------------------------------------------------------------- */
276 45 : ScanForEntities(osHeaderFile, "HEADER");
277 45 : ScanForEntities(osTrailerFile, "TRAILER");
278 :
279 : /* -------------------------------------------------------------------- */
280 : /* Attempt to read the template header file so we have a list */
281 : /* of layers, linestyles and blocks. */
282 : /* -------------------------------------------------------------------- */
283 45 : if (!oHeaderDS.Open(osHeaderFile, true, nullptr))
284 0 : return FALSE;
285 :
286 : /* -------------------------------------------------------------------- */
287 : /* Create the output file. */
288 : /* -------------------------------------------------------------------- */
289 45 : fp = VSIFOpenExL(pszFilename, "w+", true);
290 :
291 45 : if (fp == nullptr)
292 : {
293 1 : CPLError(CE_Failure, CPLE_OpenFailed,
294 : "Failed to open '%s' for writing: %s", pszFilename,
295 : VSIGetLastErrorMsg());
296 1 : return FALSE;
297 : }
298 :
299 : /* -------------------------------------------------------------------- */
300 : /* Establish the temporary file. */
301 : /* -------------------------------------------------------------------- */
302 44 : osTempFilename = pszFilename;
303 44 : osTempFilename += ".tmp";
304 :
305 44 : fpTemp = VSIFOpenL(osTempFilename, "w");
306 44 : if (fpTemp == nullptr)
307 : {
308 0 : CPLError(CE_Failure, CPLE_OpenFailed,
309 : "Failed to open '%s' for writing.", osTempFilename.c_str());
310 0 : return FALSE;
311 : }
312 :
313 44 : return TRUE;
314 : }
315 :
316 : /************************************************************************/
317 : /* ICreateLayer() */
318 : /************************************************************************/
319 :
320 : OGRLayer *
321 61 : OGRDXFWriterDS::ICreateLayer(const char *pszName,
322 : const OGRGeomFieldDefn * /*poGeomFieldDefn*/,
323 : CSLConstList /*papszOptions*/)
324 :
325 : {
326 61 : if (EQUAL(pszName, "blocks") && poBlocksLayer == nullptr)
327 : {
328 2 : poBlocksLayer = new OGRDXFBlocksWriterLayer(this);
329 2 : return poBlocksLayer;
330 : }
331 59 : else if (poLayer == nullptr)
332 : {
333 43 : poLayer = new OGRDXFWriterLayer(this, fpTemp);
334 43 : return poLayer;
335 : }
336 : else
337 : {
338 16 : CPLError(CE_Failure, CPLE_AppDefined,
339 : "Unable to have more than one OGR entities layer in a DXF "
340 : "file, with one options blocks layer.");
341 16 : return nullptr;
342 : }
343 : }
344 :
345 : /************************************************************************/
346 : /* WriteValue() */
347 : /************************************************************************/
348 :
349 35376 : static bool WriteValue(VSILFILE *fp, int nCode, const char *pszLine)
350 :
351 : {
352 : char szLinePair[300];
353 :
354 35376 : snprintf(szLinePair, sizeof(szLinePair), "%3d\n%s\n", nCode, pszLine);
355 35376 : size_t nLen = strlen(szLinePair);
356 35376 : if (VSIFWriteL(szLinePair, 1, nLen, fp) != nLen)
357 : {
358 0 : CPLError(CE_Failure, CPLE_FileIO,
359 : "Attempt to write line to DXF file failed, disk full?.");
360 0 : return false;
361 : }
362 :
363 35376 : return true;
364 : }
365 :
366 : /************************************************************************/
367 : /* WriteValue() */
368 : /************************************************************************/
369 :
370 184 : static bool WriteValue(VSILFILE *fp, int nCode, double dfValue)
371 :
372 : {
373 : char szLinePair[64];
374 :
375 184 : CPLsnprintf(szLinePair, sizeof(szLinePair), "%3d\n%.15g\n", nCode, dfValue);
376 184 : size_t nLen = strlen(szLinePair);
377 184 : if (VSIFWriteL(szLinePair, 1, nLen, fp) != nLen)
378 : {
379 0 : CPLError(CE_Failure, CPLE_FileIO,
380 : "Attempt to write line to DXF file failed, disk full?.");
381 0 : return false;
382 : }
383 :
384 184 : return true;
385 : }
386 :
387 : /************************************************************************/
388 : /* TransferUpdateHeader() */
389 : /************************************************************************/
390 :
391 44 : bool OGRDXFWriterDS::TransferUpdateHeader(VSILFILE *fpOut)
392 :
393 : {
394 44 : oHeaderDS.ResetReadPointer(0);
395 :
396 : // We don't like non-finite extents. In this case, just write a generic
397 : // bounding box. Most CAD programs probably ignore this anyway.
398 44 : if (!std::isfinite(oGlobalEnvelope.MinX) ||
399 26 : !std::isfinite(oGlobalEnvelope.MinY) ||
400 96 : !std::isfinite(oGlobalEnvelope.MaxX) ||
401 26 : !std::isfinite(oGlobalEnvelope.MaxY))
402 : {
403 18 : oGlobalEnvelope.MinX = 0.0;
404 18 : oGlobalEnvelope.MinY = 0.0;
405 18 : oGlobalEnvelope.MaxX = 10.0;
406 18 : oGlobalEnvelope.MaxY = 10.0;
407 : }
408 :
409 : /* -------------------------------------------------------------------- */
410 : /* Copy header, inserting in new objects as needed. */
411 : /* -------------------------------------------------------------------- */
412 : char szLineBuf[257];
413 44 : int nCode = 0;
414 88 : CPLString osSection;
415 88 : CPLString osTable;
416 88 : CPLString osEntity;
417 :
418 48850 : while ((nCode = oHeaderDS.ReadValue(szLineBuf, sizeof(szLineBuf))) != -1 &&
419 24403 : osSection != "ENTITIES")
420 : {
421 24403 : if (nCode == 0 && EQUAL(szLineBuf, "ENDTAB"))
422 : {
423 : // If we are at the end of the LAYER TABLE consider inserting
424 : // missing definitions.
425 396 : if (osTable == "LAYER")
426 : {
427 44 : if (!WriteNewLayerDefinitions(fp))
428 0 : return false;
429 : }
430 :
431 : // If at the end of the BLOCK_RECORD TABLE consider inserting
432 : // missing definitions.
433 396 : if (osTable == "BLOCK_RECORD" && poBlocksLayer)
434 : {
435 2 : if (!WriteNewBlockRecords(fp))
436 0 : return false;
437 : }
438 :
439 : // If at the end of the LTYPE TABLE consider inserting
440 : // missing layer type definitions.
441 396 : if (osTable == "LTYPE")
442 : {
443 44 : if (!WriteNewLineTypeRecords(fp))
444 0 : return false;
445 : }
446 :
447 : // If at the end of the STYLE TABLE consider inserting
448 : // missing layer type definitions.
449 396 : if (osTable == "STYLE")
450 : {
451 44 : if (!WriteNewTextStyleRecords(fp))
452 0 : return false;
453 : }
454 :
455 396 : osTable = "";
456 : }
457 :
458 : // If we are at the end of the BLOCKS section, consider inserting
459 : // supplementary blocks.
460 24447 : if (nCode == 0 && osSection == "BLOCKS" && EQUAL(szLineBuf, "ENDSEC") &&
461 44 : poBlocksLayer != nullptr)
462 : {
463 2 : if (!WriteNewBlockDefinitions(fp))
464 0 : return false;
465 : }
466 :
467 : // We need to keep track of where $HANDSEED is so that we can
468 : // come back and fix it up when we have generated all entity ids.
469 24403 : if (nCode == 9 && EQUAL(szLineBuf, "$HANDSEED"))
470 : {
471 44 : if (!WriteValue(fpOut, nCode, szLineBuf))
472 0 : return false;
473 :
474 44 : nCode = oHeaderDS.ReadValue(szLineBuf, sizeof(szLineBuf));
475 :
476 : // ensure we have room to overwrite with a longer value.
477 308 : while (strlen(szLineBuf) < 8)
478 : {
479 264 : memmove(szLineBuf + 1, szLineBuf, strlen(szLineBuf) + 1);
480 264 : szLineBuf[0] = '0';
481 : }
482 :
483 44 : nHANDSEEDOffset = VSIFTellL(fpOut);
484 : }
485 :
486 : // Patch EXTMIN with minx and miny
487 24403 : if (nCode == 9 && EQUAL(szLineBuf, "$EXTMIN"))
488 : {
489 44 : if (!WriteValue(fpOut, nCode, szLineBuf))
490 0 : return false;
491 :
492 44 : nCode = oHeaderDS.ReadValue(szLineBuf, sizeof(szLineBuf));
493 44 : if (nCode == 10)
494 : {
495 44 : if (!WriteValue(fpOut, nCode, oGlobalEnvelope.MinX))
496 0 : return false;
497 :
498 44 : nCode = oHeaderDS.ReadValue(szLineBuf, sizeof(szLineBuf));
499 44 : if (nCode == 20)
500 : {
501 44 : if (!WriteValue(fpOut, nCode, oGlobalEnvelope.MinY))
502 0 : return false;
503 :
504 44 : continue;
505 : }
506 : }
507 : }
508 :
509 : // Patch EXTMAX with maxx and maxy
510 24359 : if (nCode == 9 && EQUAL(szLineBuf, "$EXTMAX"))
511 : {
512 44 : if (!WriteValue(fpOut, nCode, szLineBuf))
513 0 : return false;
514 :
515 44 : nCode = oHeaderDS.ReadValue(szLineBuf, sizeof(szLineBuf));
516 44 : if (nCode == 10)
517 : {
518 44 : if (!WriteValue(fpOut, nCode, oGlobalEnvelope.MaxX))
519 0 : return false;
520 :
521 44 : nCode = oHeaderDS.ReadValue(szLineBuf, sizeof(szLineBuf));
522 44 : if (nCode == 20)
523 : {
524 44 : if (!WriteValue(fpOut, nCode, oGlobalEnvelope.MaxY))
525 0 : return false;
526 :
527 44 : continue;
528 : }
529 : }
530 : }
531 :
532 : // Copy over the source line.
533 24315 : if (!WriteValue(fpOut, nCode, szLineBuf))
534 0 : return false;
535 :
536 : // Track what entity we are in - that is the last "code 0" object.
537 24315 : if (nCode == 0)
538 1887 : osEntity = szLineBuf;
539 :
540 : // Track what section we are in.
541 24315 : if (nCode == 0 && EQUAL(szLineBuf, "SECTION"))
542 : {
543 220 : nCode = oHeaderDS.ReadValue(szLineBuf);
544 220 : if (nCode == -1)
545 0 : break;
546 :
547 220 : if (!WriteValue(fpOut, nCode, szLineBuf))
548 0 : return false;
549 :
550 220 : osSection = szLineBuf;
551 : }
552 :
553 : // Track what TABLE we are in.
554 24315 : if (nCode == 0 && EQUAL(szLineBuf, "TABLE"))
555 : {
556 396 : nCode = oHeaderDS.ReadValue(szLineBuf);
557 396 : if (!WriteValue(fpOut, nCode, szLineBuf))
558 0 : return false;
559 :
560 396 : osTable = szLineBuf;
561 : }
562 :
563 : // If we are starting the first layer, then capture
564 : // the layer contents while copying so we can duplicate
565 : // it for any new layer definitions.
566 24359 : if (nCode == 0 && EQUAL(szLineBuf, "LAYER") && osTable == "LAYER" &&
567 44 : aosDefaultLayerText.empty())
568 : {
569 440 : do
570 : {
571 484 : anDefaultLayerCode.push_back(nCode);
572 484 : aosDefaultLayerText.push_back(szLineBuf);
573 :
574 484 : if (nCode != 0 && !WriteValue(fpOut, nCode, szLineBuf))
575 0 : return false;
576 :
577 484 : nCode = oHeaderDS.ReadValue(szLineBuf);
578 :
579 484 : if (nCode == 2 && !EQUAL(szLineBuf, "0"))
580 : {
581 0 : anDefaultLayerCode.resize(0);
582 0 : aosDefaultLayerText.resize(0);
583 0 : break;
584 : }
585 484 : } while (nCode != 0);
586 :
587 44 : oHeaderDS.UnreadValue();
588 : }
589 : }
590 :
591 44 : return true;
592 : }
593 :
594 : /************************************************************************/
595 : /* TransferUpdateTrailer() */
596 : /************************************************************************/
597 :
598 44 : bool OGRDXFWriterDS::TransferUpdateTrailer(VSILFILE *fpOut)
599 : {
600 : /* -------------------------------------------------------------------- */
601 : /* Open the file and setup a reader. */
602 : /* -------------------------------------------------------------------- */
603 44 : VSILFILE *l_fp = VSIFOpenL(osTrailerFile, "r");
604 :
605 44 : if (l_fp == nullptr)
606 0 : return false;
607 :
608 88 : OGRDXFReader oReader;
609 44 : oReader.Initialize(l_fp);
610 :
611 : /* -------------------------------------------------------------------- */
612 : /* Scan ahead to find the OBJECTS section. */
613 : /* -------------------------------------------------------------------- */
614 : char szLineBuf[257];
615 44 : int nCode = 0;
616 :
617 88 : while ((nCode = oReader.ReadValue(szLineBuf, sizeof(szLineBuf))) != -1)
618 : {
619 88 : if (nCode == 0 && EQUAL(szLineBuf, "SECTION"))
620 : {
621 44 : nCode = oReader.ReadValue(szLineBuf, sizeof(szLineBuf));
622 44 : if (nCode == 2 && EQUAL(szLineBuf, "OBJECTS"))
623 44 : break;
624 : }
625 : }
626 :
627 44 : if (nCode == -1)
628 : {
629 0 : CPLError(CE_Failure, CPLE_AppDefined,
630 : "Failed to find OBJECTS section in trailer file '%s'.",
631 : osTrailerFile.c_str());
632 0 : return false;
633 : }
634 :
635 : /* -------------------------------------------------------------------- */
636 : /* Insert the "end of section" for ENTITIES, and the start of */
637 : /* the OBJECTS section. */
638 : /* -------------------------------------------------------------------- */
639 44 : WriteValue(fpOut, 0, "ENDSEC");
640 44 : WriteValue(fpOut, 0, "SECTION");
641 44 : WriteValue(fpOut, 2, "OBJECTS");
642 :
643 : /* -------------------------------------------------------------------- */
644 : /* Copy the remainder of the file. */
645 : /* -------------------------------------------------------------------- */
646 44 : bool ret = true;
647 9460 : while ((nCode = oReader.ReadValue(szLineBuf, sizeof(szLineBuf))) != -1)
648 : {
649 9416 : if (!WriteValue(fpOut, nCode, szLineBuf))
650 : {
651 0 : ret = false;
652 0 : break;
653 : }
654 : }
655 :
656 44 : VSIFCloseL(l_fp);
657 :
658 44 : return ret;
659 : }
660 :
661 : /************************************************************************/
662 : /* FixupHANDSEED() */
663 : /* */
664 : /* Fixup the next entity id information in the $HANDSEED header */
665 : /* variable. */
666 : /************************************************************************/
667 :
668 44 : bool OGRDXFWriterDS::FixupHANDSEED(VSILFILE *fpIn)
669 :
670 : {
671 : /* -------------------------------------------------------------------- */
672 : /* What is a good next handle seed? Scan existing values. */
673 : /* -------------------------------------------------------------------- */
674 44 : unsigned int nHighestHandle = 0;
675 44 : std::set<CPLString>::iterator it;
676 :
677 1755 : for (it = aosUsedEntities.begin(); it != aosUsedEntities.end(); ++it)
678 : {
679 1711 : unsigned int nHandle = 0;
680 1711 : if (sscanf((*it).c_str(), "%x", &nHandle) == 1)
681 : {
682 1711 : if (nHandle > nHighestHandle)
683 1125 : nHighestHandle = nHandle;
684 : }
685 : }
686 :
687 : /* -------------------------------------------------------------------- */
688 : /* Read the existing handseed value, replace it, and write back. */
689 : /* -------------------------------------------------------------------- */
690 44 : if (nHANDSEEDOffset == 0)
691 0 : return false;
692 :
693 : char szWorkBuf[30];
694 44 : VSIFSeekL(fpIn, nHANDSEEDOffset, SEEK_SET);
695 44 : VSIFReadL(szWorkBuf, 1, sizeof(szWorkBuf), fpIn);
696 :
697 44 : int i = 0;
698 176 : while (szWorkBuf[i] != '\n')
699 132 : i++;
700 :
701 44 : i++;
702 44 : if (szWorkBuf[i] == '\r')
703 0 : i++;
704 :
705 44 : CPLString osNewValue;
706 :
707 44 : osNewValue.Printf("%08X", nHighestHandle + 1);
708 44 : strncpy(szWorkBuf + i, osNewValue.c_str(), osNewValue.size());
709 :
710 44 : VSIFSeekL(fpIn, nHANDSEEDOffset, SEEK_SET);
711 44 : VSIFWriteL(szWorkBuf, 1, sizeof(szWorkBuf), fp);
712 :
713 44 : return true;
714 : }
715 :
716 : /************************************************************************/
717 : /* WriteNewLayerDefinitions() */
718 : /************************************************************************/
719 :
720 44 : bool OGRDXFWriterDS::WriteNewLayerDefinitions(VSILFILE *fpOut)
721 :
722 : {
723 44 : const int nNewLayers = CSLCount(papszLayersToCreate);
724 :
725 47 : for (int iLayer = 0; iLayer < nNewLayers; iLayer++)
726 : {
727 3 : bool bIsDefPoints = false;
728 3 : bool bWrote290 = false;
729 36 : for (unsigned i = 0; i < aosDefaultLayerText.size(); i++)
730 : {
731 33 : if (anDefaultLayerCode[i] == 2)
732 : {
733 3 : if (EQUAL(papszLayersToCreate[iLayer], "DEFPOINTS"))
734 0 : bIsDefPoints = true;
735 :
736 3 : if (!WriteValue(fpOut, 2, papszLayersToCreate[iLayer]))
737 0 : return false;
738 : }
739 30 : else if (anDefaultLayerCode[i] == 5)
740 : {
741 : unsigned int nIgnored;
742 3 : if (!WriteEntityID(fpOut, nIgnored))
743 0 : return false;
744 : }
745 : else
746 : {
747 27 : if (anDefaultLayerCode[i] == 290)
748 0 : bWrote290 = true;
749 :
750 27 : if (!WriteValue(fpOut, anDefaultLayerCode[i],
751 27 : aosDefaultLayerText[i]))
752 0 : return false;
753 : }
754 : }
755 3 : if (bIsDefPoints && !bWrote290)
756 : {
757 : // The Defpoints layer must be explicitly set to not plotted to
758 : // please Autocad. See https://trac.osgeo.org/gdal/ticket/7078
759 0 : if (!WriteValue(fpOut, 290, "0"))
760 0 : return false;
761 : }
762 : }
763 :
764 44 : return true;
765 : }
766 :
767 : /************************************************************************/
768 : /* WriteNewLineTypeRecords() */
769 : /************************************************************************/
770 :
771 44 : bool OGRDXFWriterDS::WriteNewLineTypeRecords(VSILFILE *fpIn)
772 :
773 : {
774 44 : if (poLayer == nullptr)
775 1 : return true;
776 :
777 : const std::map<CPLString, std::vector<double>> &oNewLineTypes =
778 43 : poLayer->GetNewLineTypeMap();
779 :
780 43 : bool bRet = true;
781 45 : for (const auto &oPair : oNewLineTypes)
782 : {
783 2 : bRet &= WriteValue(fpIn, 0, "LTYPE");
784 : unsigned int nIgnored;
785 2 : bRet &= WriteEntityID(fpIn, nIgnored);
786 2 : bRet &= WriteValue(fpIn, 100, "AcDbSymbolTableRecord");
787 2 : bRet &= WriteValue(fpIn, 100, "AcDbLinetypeTableRecord");
788 2 : bRet &= WriteValue(fpIn, 2, oPair.first);
789 2 : bRet &= WriteValue(fpIn, 70, "0");
790 2 : bRet &= WriteValue(fpIn, 3, "");
791 2 : bRet &= WriteValue(fpIn, 72, "65");
792 2 : bRet &= WriteValue(fpIn, 73, static_cast<int>(oPair.second.size()));
793 :
794 2 : double dfTotalLength = 0.0;
795 6 : for (const double &dfSegment : oPair.second)
796 4 : dfTotalLength += fabs(dfSegment);
797 2 : bRet &= WriteValue(fpIn, 40, dfTotalLength);
798 :
799 6 : for (const double &dfSegment : oPair.second)
800 : {
801 4 : bRet &= WriteValue(fpIn, 49, dfSegment);
802 4 : bRet &= WriteValue(fpIn, 74, "0");
803 : }
804 : }
805 :
806 43 : return bRet;
807 : }
808 :
809 : /************************************************************************/
810 : /* WriteNewTextStyleRecords() */
811 : /************************************************************************/
812 :
813 44 : bool OGRDXFWriterDS::WriteNewTextStyleRecords(VSILFILE *fpIn)
814 :
815 : {
816 44 : if (poLayer == nullptr)
817 1 : return true;
818 :
819 43 : auto &oNewTextStyles = poLayer->GetNewTextStyleMap();
820 :
821 43 : bool bRet = true;
822 44 : for (auto &oPair : oNewTextStyles)
823 : {
824 1 : bRet &= WriteValue(fpIn, 0, "STYLE");
825 : unsigned int nIgnored;
826 1 : bRet &= WriteEntityID(fpIn, nIgnored);
827 1 : bRet &= WriteValue(fpIn, 100, "AcDbSymbolTableRecord");
828 1 : bRet &= WriteValue(fpIn, 100, "AcDbTextStyleTableRecord");
829 1 : bRet &= WriteValue(fpIn, 2, oPair.first);
830 1 : bRet &= WriteValue(fpIn, 70, "0");
831 1 : bRet &= WriteValue(fpIn, 40, "0.0");
832 :
833 1 : if (oPair.second.count("Width"))
834 1 : bRet &= WriteValue(fpIn, 41, oPair.second["Width"]);
835 : else
836 0 : bRet &= WriteValue(fpIn, 41, "1.0");
837 :
838 1 : bRet &= WriteValue(fpIn, 50, "0.0");
839 1 : bRet &= WriteValue(fpIn, 71, "0");
840 1 : bRet &= WriteValue(fpIn, 1001, "ACAD");
841 :
842 1 : if (oPair.second.count("Font"))
843 1 : bRet &= WriteValue(fpIn, 1000, oPair.second["Font"]);
844 :
845 1 : int nStyleValue = 0;
846 1 : if (oPair.second.count("Italic") && oPair.second["Italic"] == "1")
847 0 : nStyleValue |= 0x1000000;
848 1 : if (oPair.second.count("Bold") && oPair.second["Bold"] == "1")
849 1 : nStyleValue |= 0x2000000;
850 1 : bRet &= WriteValue(fpIn, 1071,
851 2 : CPLString().Printf("%d", nStyleValue).c_str());
852 : }
853 :
854 43 : return bRet;
855 : }
856 :
857 : /************************************************************************/
858 : /* WriteNewBlockRecords() */
859 : /************************************************************************/
860 :
861 2 : bool OGRDXFWriterDS::WriteNewBlockRecords(VSILFILE *fpIn)
862 :
863 : {
864 2 : std::set<CPLString> aosAlreadyHandled;
865 :
866 : /* ==================================================================== */
867 : /* Loop over all block objects written via the blocks layer. */
868 : /* ==================================================================== */
869 2 : bool bRet = true;
870 6 : for (size_t iBlock = 0; iBlock < poBlocksLayer->apoBlocks.size(); iBlock++)
871 : {
872 4 : OGRFeature *poThisBlockFeat = poBlocksLayer->apoBlocks[iBlock];
873 :
874 : /* --------------------------------------------------------------------
875 : */
876 : /* Is this block already defined in the template header? */
877 : /* --------------------------------------------------------------------
878 : */
879 4 : CPLString osBlockName = poThisBlockFeat->GetFieldAsString("Block");
880 :
881 4 : if (oHeaderDS.LookupBlock(osBlockName) != nullptr)
882 0 : continue;
883 :
884 : /* --------------------------------------------------------------------
885 : */
886 : /* Have we already written a BLOCK_RECORD for this block? */
887 : /* --------------------------------------------------------------------
888 : */
889 4 : if (aosAlreadyHandled.find(osBlockName) != aosAlreadyHandled.end())
890 0 : continue;
891 :
892 4 : aosAlreadyHandled.insert(osBlockName);
893 :
894 : /* --------------------------------------------------------------------
895 : */
896 : /* Write the block record. */
897 : /* --------------------------------------------------------------------
898 : */
899 4 : bRet &= WriteValue(fpIn, 0, "BLOCK_RECORD");
900 : unsigned int nIgnored;
901 4 : bRet &= WriteEntityID(fpIn, nIgnored);
902 4 : bRet &= WriteValue(fpIn, 100, "AcDbSymbolTableRecord");
903 4 : bRet &= WriteValue(fpIn, 100, "AcDbBlockTableRecord");
904 4 : bRet &= WriteValue(fpIn, 2, poThisBlockFeat->GetFieldAsString("Block"));
905 4 : bRet &= WriteValue(fpIn, 340, "0");
906 : }
907 :
908 4 : return bRet;
909 : }
910 :
911 : /************************************************************************/
912 : /* WriteNewBlockDefinitions() */
913 : /************************************************************************/
914 :
915 2 : bool OGRDXFWriterDS::WriteNewBlockDefinitions(VSILFILE *fpIn)
916 :
917 : {
918 2 : if (poLayer == nullptr)
919 1 : poLayer = new OGRDXFWriterLayer(this, fpTemp);
920 2 : poLayer->ResetFP(fpIn);
921 :
922 : /* ==================================================================== */
923 : /* Loop over all block objects written via the blocks layer. */
924 : /* ==================================================================== */
925 2 : bool bRet = true;
926 6 : for (size_t iBlock = 0; iBlock < poBlocksLayer->apoBlocks.size(); iBlock++)
927 : {
928 4 : OGRFeature *poThisBlockFeat = poBlocksLayer->apoBlocks[iBlock];
929 :
930 : /* --------------------------------------------------------------------
931 : */
932 : /* Is this block already defined in the template header? */
933 : /* --------------------------------------------------------------------
934 : */
935 4 : CPLString osBlockName = poThisBlockFeat->GetFieldAsString("Block");
936 :
937 4 : if (oHeaderDS.LookupBlock(osBlockName) != nullptr)
938 0 : continue;
939 :
940 : /* --------------------------------------------------------------------
941 : */
942 : /* Write the block definition preamble. */
943 : /* --------------------------------------------------------------------
944 : */
945 4 : CPLDebug("DXF", "Writing BLOCK definition for '%s'.",
946 : poThisBlockFeat->GetFieldAsString("Block"));
947 :
948 4 : bRet &= WriteValue(fpIn, 0, "BLOCK");
949 : unsigned int nIgnored;
950 4 : bRet &= WriteEntityID(fpIn, nIgnored);
951 4 : bRet &= WriteValue(fpIn, 100, "AcDbEntity");
952 4 : if (strlen(poThisBlockFeat->GetFieldAsString("Layer")) > 0)
953 0 : bRet &=
954 0 : WriteValue(fpIn, 8, poThisBlockFeat->GetFieldAsString("Layer"));
955 : else
956 4 : bRet &= WriteValue(fpIn, 8, "0");
957 4 : bRet &= WriteValue(fpIn, 100, "AcDbBlockBegin");
958 4 : bRet &= WriteValue(fpIn, 2, poThisBlockFeat->GetFieldAsString("Block"));
959 4 : bRet &= WriteValue(fpIn, 70, "0");
960 :
961 : // Origin
962 4 : bRet &= WriteValue(fpIn, 10, "0.0");
963 4 : bRet &= WriteValue(fpIn, 20, "0.0");
964 4 : bRet &= WriteValue(fpIn, 30, "0.0");
965 :
966 4 : bRet &= WriteValue(fpIn, 3, poThisBlockFeat->GetFieldAsString("Block"));
967 4 : bRet &= WriteValue(fpIn, 1, "");
968 :
969 : /* --------------------------------------------------------------------
970 : */
971 : /* Write out the feature entities. */
972 : /* --------------------------------------------------------------------
973 : */
974 4 : if (poLayer->CreateFeature(poThisBlockFeat) != OGRERR_NONE)
975 0 : return false;
976 :
977 : /* --------------------------------------------------------------------
978 : */
979 : /* Write out following features if they are the same block. */
980 : /* --------------------------------------------------------------------
981 : */
982 6 : while (iBlock < poBlocksLayer->apoBlocks.size() - 1 &&
983 2 : EQUAL(poBlocksLayer->apoBlocks[iBlock + 1]->GetFieldAsString(
984 : "Block"),
985 : osBlockName))
986 : {
987 0 : iBlock++;
988 :
989 0 : if (poLayer->CreateFeature(poBlocksLayer->apoBlocks[iBlock]) !=
990 : OGRERR_NONE)
991 0 : return false;
992 : }
993 :
994 : /* --------------------------------------------------------------------
995 : */
996 : /* Write out the block definition postamble. */
997 : /* --------------------------------------------------------------------
998 : */
999 4 : bRet &= WriteValue(fpIn, 0, "ENDBLK");
1000 4 : bRet &= WriteEntityID(fpIn, nIgnored);
1001 4 : bRet &= WriteValue(fpIn, 100, "AcDbEntity");
1002 4 : if (strlen(poThisBlockFeat->GetFieldAsString("Layer")) > 0)
1003 0 : bRet &=
1004 0 : WriteValue(fpIn, 8, poThisBlockFeat->GetFieldAsString("Layer"));
1005 : else
1006 4 : bRet &= WriteValue(fpIn, 8, "0");
1007 4 : bRet &= WriteValue(fpIn, 100, "AcDbBlockEnd");
1008 : }
1009 :
1010 2 : return bRet;
1011 : }
1012 :
1013 : /************************************************************************/
1014 : /* ScanForEntities() */
1015 : /* */
1016 : /* Scan the indicated file for entity ids ("5" records) and */
1017 : /* build them up as a set so we can be careful to avoid */
1018 : /* creating new entities with conflicting ids. */
1019 : /************************************************************************/
1020 :
1021 90 : void OGRDXFWriterDS::ScanForEntities(const char *pszFilename,
1022 : const char *pszTarget)
1023 :
1024 : {
1025 : /* -------------------------------------------------------------------- */
1026 : /* Open the file and setup a reader. */
1027 : /* -------------------------------------------------------------------- */
1028 90 : VSILFILE *l_fp = VSIFOpenL(pszFilename, "r");
1029 :
1030 90 : if (l_fp == nullptr)
1031 0 : return;
1032 :
1033 180 : OGRDXFReader oReader;
1034 90 : oReader.Initialize(l_fp);
1035 :
1036 : /* -------------------------------------------------------------------- */
1037 : /* Add every code "5" line to our entities list. */
1038 : /* -------------------------------------------------------------------- */
1039 : char szLineBuf[257];
1040 90 : int nCode = 0;
1041 90 : const char *pszPortion = "HEADER";
1042 :
1043 35826 : while ((nCode = oReader.ReadValue(szLineBuf, sizeof(szLineBuf))) != -1)
1044 : {
1045 35736 : if ((nCode == 5 || nCode == 105) && EQUAL(pszTarget, pszPortion))
1046 : {
1047 3132 : CPLString osEntity(szLineBuf);
1048 :
1049 1566 : if (CheckEntityID(osEntity))
1050 6 : CPLDebug("DXF", "Encountered entity '%s' multiple times.",
1051 : osEntity.c_str());
1052 : else
1053 1560 : aosUsedEntities.insert(osEntity);
1054 : }
1055 :
1056 35736 : if (nCode == 0 && EQUAL(szLineBuf, "SECTION"))
1057 : {
1058 270 : nCode = oReader.ReadValue(szLineBuf, sizeof(szLineBuf));
1059 270 : if (nCode == 2 && EQUAL(szLineBuf, "ENTITIES"))
1060 45 : pszPortion = "BODY";
1061 270 : if (nCode == 2 && EQUAL(szLineBuf, "OBJECTS"))
1062 45 : pszPortion = "TRAILER";
1063 : }
1064 : }
1065 :
1066 90 : VSIFCloseL(l_fp);
1067 : }
1068 :
1069 : /************************************************************************/
1070 : /* CheckEntityID() */
1071 : /* */
1072 : /* Does the mentioned entity already exist? */
1073 : /************************************************************************/
1074 :
1075 1766 : bool OGRDXFWriterDS::CheckEntityID(const char *pszEntityID)
1076 :
1077 : {
1078 1766 : std::set<CPLString>::iterator it;
1079 :
1080 1766 : it = aosUsedEntities.find(pszEntityID);
1081 1766 : return it != aosUsedEntities.end();
1082 : }
1083 :
1084 : /************************************************************************/
1085 : /* WriteEntityID() */
1086 : /************************************************************************/
1087 :
1088 185 : bool OGRDXFWriterDS::WriteEntityID(VSILFILE *fpIn, unsigned int &nAssignedFID,
1089 : GIntBig nPreferredFID)
1090 :
1091 : {
1092 370 : CPLString osEntityID;
1093 :
1094 : // From https://github.com/OSGeo/gdal/issues/11299 it seems that 0 is an
1095 : // invalid handle value.
1096 201 : if (nPreferredFID > 0 &&
1097 : nPreferredFID <=
1098 16 : static_cast<GIntBig>(std::numeric_limits<unsigned int>::max()))
1099 : {
1100 :
1101 16 : osEntityID.Printf("%X", static_cast<unsigned int>(nPreferredFID));
1102 16 : if (!CheckEntityID(osEntityID))
1103 : {
1104 1 : aosUsedEntities.insert(osEntityID);
1105 1 : if (!WriteValue(fpIn, 5, osEntityID))
1106 0 : return false;
1107 1 : nAssignedFID = static_cast<unsigned int>(nPreferredFID);
1108 1 : return true;
1109 : }
1110 : }
1111 :
1112 0 : do
1113 : {
1114 184 : osEntityID.Printf("%X", nNextFID++);
1115 184 : } while (CheckEntityID(osEntityID));
1116 :
1117 184 : aosUsedEntities.insert(osEntityID);
1118 184 : if (!WriteValue(fpIn, 5, osEntityID))
1119 0 : return false;
1120 :
1121 184 : nAssignedFID = nNextFID - 1;
1122 184 : return true;
1123 : }
1124 :
1125 : /************************************************************************/
1126 : /* UpdateExtent() */
1127 : /************************************************************************/
1128 :
1129 175 : void OGRDXFWriterDS::UpdateExtent(OGREnvelope *psEnvelope)
1130 : {
1131 175 : oGlobalEnvelope.Merge(*psEnvelope);
1132 175 : }
|