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