Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/dxf/ogrdxfwriterlayer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  DXF Translator
4
 * Purpose:  Implements OGRDXFWriterLayer - the OGRLayer 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) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "ogr_dxf.h"
16
#include "cpl_conv.h"
17
#include "cpl_string.h"
18
#include "ogr_featurestyle.h"
19
20
#include <cstdlib>
21
22
/************************************************************************/
23
/*                         OGRDXFWriterLayer()                          */
24
/************************************************************************/
25
26
OGRDXFWriterLayer::OGRDXFWriterLayer(OGRDXFWriterDS *poDSIn, VSILFILE *fpIn)
27
0
    : fp(fpIn),
28
0
      poFeatureDefn(nullptr),  // TODO(schwehr): Can I move the new here?
29
0
      poDS(poDSIn)
30
0
{
31
0
    nNextAutoID = 1;
32
0
    bWriteHatch = CPLTestBool(CPLGetConfigOption("DXF_WRITE_HATCH", "YES"));
33
34
0
    poFeatureDefn = new OGRFeatureDefn("entities");
35
0
    poFeatureDefn->Reference();
36
37
0
    OGRDXFDataSource::AddStandardFields(poFeatureDefn, ODFM_IncludeBlockFields);
38
0
}
39
40
/************************************************************************/
41
/*                         ~OGRDXFWriterLayer()                         */
42
/************************************************************************/
43
44
OGRDXFWriterLayer::~OGRDXFWriterLayer()
45
46
0
{
47
0
    if (poFeatureDefn)
48
0
        poFeatureDefn->Release();
49
0
}
50
51
/************************************************************************/
52
/*                              ResetFP()                               */
53
/*                                                                      */
54
/*      Redirect output.  Mostly used for writing block definitions.    */
55
/************************************************************************/
56
57
void OGRDXFWriterLayer::ResetFP(VSILFILE *fpNew)
58
59
0
{
60
0
    fp = fpNew;
61
0
}
62
63
/************************************************************************/
64
/*                           TestCapability()                           */
65
/************************************************************************/
66
67
bool OGRDXFWriterLayer::TestCapability(const char *pszCap) const
68
69
0
{
70
0
    if (EQUAL(pszCap, OLCStringsAsUTF8))
71
0
        return TRUE;
72
0
    else if (EQUAL(pszCap, OLCSequentialWrite))
73
0
        return TRUE;
74
0
    else
75
0
        return FALSE;
76
0
}
77
78
/************************************************************************/
79
/*                            CreateField()                             */
80
/*                                                                      */
81
/*      This is really a dummy as our fields are precreated.            */
82
/************************************************************************/
83
84
OGRErr OGRDXFWriterLayer::CreateField(const OGRFieldDefn *poField,
85
                                      int bApproxOK)
86
87
0
{
88
0
    if (poFeatureDefn->GetFieldIndex(poField->GetNameRef()) >= 0 && bApproxOK)
89
0
        return OGRERR_NONE;
90
0
    if (EQUAL(poField->GetNameRef(), "OGR_STYLE"))
91
0
    {
92
0
        poFeatureDefn->AddFieldDefn(poField);
93
0
        return OGRERR_NONE;
94
0
    }
95
96
0
    CPLError(CE_Failure, CPLE_AppDefined,
97
0
             "DXF layer does not support arbitrary field creation, field '%s' "
98
0
             "not created.",
99
0
             poField->GetNameRef());
100
101
0
    return OGRERR_FAILURE;
102
0
}
103
104
/************************************************************************/
105
/*                             WriteValue()                             */
106
/************************************************************************/
107
108
int OGRDXFWriterLayer::WriteValue(int nCode, const char *pszValue)
109
110
0
{
111
0
    CPLString osLinePair;
112
113
0
    osLinePair.Printf("%3d\n", nCode);
114
115
0
    if (strlen(pszValue) < 255)
116
0
        osLinePair += pszValue;
117
0
    else
118
0
        osLinePair.append(pszValue, 255);
119
120
0
    osLinePair += "\n";
121
122
0
    return VSIFWriteL(osLinePair.c_str(), 1, osLinePair.size(), fp) ==
123
0
           osLinePair.size();
124
0
}
125
126
/************************************************************************/
127
/*                             WriteValue()                             */
128
/************************************************************************/
129
130
int OGRDXFWriterLayer::WriteValue(int nCode, int nValue)
131
132
0
{
133
0
    CPLString osLinePair;
134
135
0
    osLinePair.Printf("%3d\n%d\n", nCode, nValue);
136
137
0
    return VSIFWriteL(osLinePair.c_str(), 1, osLinePair.size(), fp) ==
138
0
           osLinePair.size();
139
0
}
140
141
/************************************************************************/
142
/*                             WriteValue()                             */
143
/************************************************************************/
144
145
int OGRDXFWriterLayer::WriteValue(int nCode, double dfValue)
146
147
0
{
148
0
    char szLinePair[64];
149
150
0
    CPLsnprintf(szLinePair, sizeof(szLinePair), "%3d\n%.15g\n", nCode, dfValue);
151
0
    size_t nLen = strlen(szLinePair);
152
153
0
    return VSIFWriteL(szLinePair, 1, nLen, fp) == nLen;
154
0
}
155
156
/************************************************************************/
157
/*                             WriteCore()                              */
158
/*                                                                      */
159
/*      Write core fields common to all sorts of elements.              */
160
/************************************************************************/
161
162
OGRErr OGRDXFWriterLayer::WriteCore(OGRFeature *poFeature,
163
                                    const CorePropertiesType &oCoreProperties)
164
165
0
{
166
    /* -------------------------------------------------------------------- */
167
    /*      Write out an entity id.  I'm not sure why this is critical,     */
168
    /*      but it seems that VoloView will just quietly fail to open       */
169
    /*      dxf files without entity ids set on most/all entities.          */
170
    /*      Also, for reasons I don't understand these ids seem to have     */
171
    /*      to start somewhere around 0x50 hex (80 decimal).                */
172
    /* -------------------------------------------------------------------- */
173
0
    unsigned int nGotFID = 0;
174
0
    poDS->WriteEntityID(fp, nGotFID, poFeature->GetFID());
175
0
    poFeature->SetFID(nGotFID);
176
177
0
    WriteValue(100, "AcDbEntity");
178
179
    /* -------------------------------------------------------------------- */
180
    /*      For now we assign everything to the default layer - layer       */
181
    /*      "0" - if there is no layer property on the source features.     */
182
    /* -------------------------------------------------------------------- */
183
0
    const char *pszLayer = poFeature->GetFieldAsString("Layer");
184
0
    if (pszLayer == nullptr || strlen(pszLayer) == 0)
185
0
    {
186
0
        WriteValue(8, "0");
187
0
    }
188
0
    else
189
0
    {
190
0
        CPLString osSanitizedLayer(pszLayer);
191
        // Replaced restricted characters with underscore
192
        // See
193
        // http://docs.autodesk.com/ACD/2010/ENU/AutoCAD%202010%20User%20Documentation/index.html?url=WS1a9193826455f5ffa23ce210c4a30acaf-7345.htm,topicNumber=d0e41665
194
0
        const char achForbiddenChars[] = {'<', '>', '/', '\\', '"', ':',
195
0
                                          ';', '?', '*', '|',  '=', '\''};
196
0
        for (size_t i = 0; i < CPL_ARRAYSIZE(achForbiddenChars); ++i)
197
0
        {
198
0
            osSanitizedLayer.replaceAll(achForbiddenChars[i], '_');
199
0
        }
200
201
        // also remove newline characters (#15067)
202
0
        osSanitizedLayer.replaceAll("\r\n", "_");
203
0
        osSanitizedLayer.replaceAll('\r', '_');
204
0
        osSanitizedLayer.replaceAll('\n', '_');
205
206
0
        auto osExists =
207
0
            poDS->oHeaderDS.LookupLayerProperty(osSanitizedLayer, "Exists");
208
0
        if (!osExists &&
209
0
            CSLFindString(poDS->papszLayersToCreate, osSanitizedLayer) == -1)
210
0
        {
211
0
            poDS->papszLayersToCreate =
212
0
                CSLAddString(poDS->papszLayersToCreate, osSanitizedLayer);
213
0
        }
214
215
0
        WriteValue(8, osSanitizedLayer);
216
0
    }
217
218
0
    for (const auto &oProp : oCoreProperties)
219
0
    {
220
0
        if (oProp.first == PROP_RGBA_COLOR)
221
0
        {
222
0
            bool bPerfectMatch = false;
223
0
            const char *pszColor = oProp.second.c_str();
224
0
            const int nColor = ColorStringToDXFColor(pszColor, bPerfectMatch);
225
0
            if (nColor >= 0)
226
0
            {
227
0
                WriteValue(62, nColor);
228
229
0
                unsigned int nRed = 0;
230
0
                unsigned int nGreen = 0;
231
0
                unsigned int nBlue = 0;
232
0
                unsigned int nOpacity = 255;
233
234
0
                const int nCount = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
235
0
                                          &nGreen, &nBlue, &nOpacity);
236
0
                if (nCount >= 3 && !bPerfectMatch)
237
0
                {
238
0
                    WriteValue(420, static_cast<int>(nBlue | (nGreen << 8) |
239
0
                                                     (nRed << 16)));
240
0
                }
241
0
                if (nCount == 4)
242
0
                {
243
0
                    WriteValue(440, static_cast<int>(nOpacity | (2 << 24)));
244
0
                }
245
0
            }
246
0
        }
247
0
        else
248
0
        {
249
            // If this happens, this is a coding error
250
0
            CPLError(CE_Failure, CPLE_AppDefined,
251
0
                     "BUG! Unhandled core property %d", oProp.first);
252
0
        }
253
0
    }
254
255
0
    return OGRERR_NONE;
256
0
}
257
258
/************************************************************************/
259
/*                            WriteINSERT()                             */
260
/************************************************************************/
261
262
OGRErr OGRDXFWriterLayer::WriteINSERT(OGRFeature *poFeature)
263
264
0
{
265
0
    CorePropertiesType oCoreProperties;
266
267
    // Write style symbol color
268
0
    OGRStyleTool *poTool = nullptr;
269
0
    OGRStyleMgr oSM;
270
0
    if (poFeature->GetStyleString() != nullptr)
271
0
    {
272
0
        oSM.InitFromFeature(poFeature);
273
274
0
        if (oSM.GetPartCount() > 0)
275
0
            poTool = oSM.GetPart(0);
276
0
    }
277
0
    if (poTool && poTool->GetType() == OGRSTCSymbol)
278
0
    {
279
0
        OGRStyleSymbol *poSymbol = cpl::down_cast<OGRStyleSymbol *>(poTool);
280
0
        GBool bDefault;
281
0
        const char *pszColor = poSymbol->Color(bDefault);
282
0
        if (pszColor && !bDefault)
283
0
            oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
284
0
    }
285
0
    delete poTool;
286
287
0
    WriteValue(0, "INSERT");
288
0
    WriteCore(poFeature, oCoreProperties);
289
0
    WriteValue(100, "AcDbBlockReference");
290
0
    WriteValue(2, poFeature->GetFieldAsString("BlockName"));
291
292
    /* -------------------------------------------------------------------- */
293
    /*      Write location in OCS.                                          */
294
    /* -------------------------------------------------------------------- */
295
0
    int nCoordCount = 0;
296
0
    const double *padfCoords =
297
0
        poFeature->GetFieldAsDoubleList("BlockOCSCoords", &nCoordCount);
298
299
0
    if (nCoordCount == 3)
300
0
    {
301
0
        WriteValue(10, padfCoords[0]);
302
0
        WriteValue(20, padfCoords[1]);
303
0
        if (!WriteValue(30, padfCoords[2]))
304
0
            return OGRERR_FAILURE;
305
0
    }
306
0
    else
307
0
    {
308
        // We don't have an OCS; we will just assume that the location of
309
        // the geometry (in WCS) is the correct insertion point.
310
0
        OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
311
312
0
        WriteValue(10, poPoint->getX());
313
0
        if (!WriteValue(20, poPoint->getY()))
314
0
            return OGRERR_FAILURE;
315
316
0
        if (poPoint->getGeometryType() == wkbPoint25D)
317
0
        {
318
0
            if (!WriteValue(30, poPoint->getZ()))
319
0
                return OGRERR_FAILURE;
320
0
        }
321
0
    }
322
323
    /* -------------------------------------------------------------------- */
324
    /*      Write scaling.                                                  */
325
    /* -------------------------------------------------------------------- */
326
0
    int nScaleCount = 0;
327
0
    const double *padfScale =
328
0
        poFeature->GetFieldAsDoubleList("BlockScale", &nScaleCount);
329
330
0
    if (nScaleCount == 3)
331
0
    {
332
0
        WriteValue(41, padfScale[0]);
333
0
        WriteValue(42, padfScale[1]);
334
0
        WriteValue(43, padfScale[2]);
335
0
    }
336
337
    /* -------------------------------------------------------------------- */
338
    /*      Write rotation.                                                 */
339
    /* -------------------------------------------------------------------- */
340
0
    const double dfAngle = poFeature->GetFieldAsDouble("BlockAngle");
341
342
0
    if (dfAngle != 0.0)
343
0
    {
344
0
        WriteValue(50, dfAngle);  // degrees
345
0
    }
346
347
    /* -------------------------------------------------------------------- */
348
    /*      Write OCS normal vector.                                        */
349
    /* -------------------------------------------------------------------- */
350
0
    int nOCSCount = 0;
351
0
    const double *padfOCS =
352
0
        poFeature->GetFieldAsDoubleList("BlockOCSNormal", &nOCSCount);
353
354
0
    if (nOCSCount == 3)
355
0
    {
356
0
        WriteValue(210, padfOCS[0]);
357
0
        WriteValue(220, padfOCS[1]);
358
0
        WriteValue(230, padfOCS[2]);
359
0
    }
360
361
0
    return OGRERR_NONE;
362
0
}
363
364
/************************************************************************/
365
/*                             WritePOINT()                             */
366
/************************************************************************/
367
368
OGRErr OGRDXFWriterLayer::WritePOINT(OGRFeature *poFeature)
369
370
0
{
371
0
    CorePropertiesType oCoreProperties;
372
373
    // Write style pen color
374
0
    OGRStyleTool *poTool = nullptr;
375
0
    OGRStyleMgr oSM;
376
0
    if (poFeature->GetStyleString() != nullptr)
377
0
    {
378
0
        oSM.InitFromFeature(poFeature);
379
380
0
        if (oSM.GetPartCount() > 0)
381
0
            poTool = oSM.GetPart(0);
382
0
    }
383
0
    if (poTool && poTool->GetType() == OGRSTCPen)
384
0
    {
385
0
        OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
386
0
        GBool bDefault;
387
0
        const char *pszColor = poPen->Color(bDefault);
388
0
        if (pszColor && !bDefault)
389
0
            oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
390
0
    }
391
0
    delete poTool;
392
393
0
    WriteValue(0, "POINT");
394
0
    WriteCore(poFeature, oCoreProperties);
395
0
    WriteValue(100, "AcDbPoint");
396
397
0
    OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
398
399
0
    WriteValue(10, poPoint->getX());
400
0
    if (!WriteValue(20, poPoint->getY()))
401
0
        return OGRERR_FAILURE;
402
403
0
    if (poPoint->getGeometryType() == wkbPoint25D)
404
0
    {
405
0
        if (!WriteValue(30, poPoint->getZ()))
406
0
            return OGRERR_FAILURE;
407
0
    }
408
409
0
    return OGRERR_NONE;
410
0
}
411
412
/************************************************************************/
413
/*                             TextEscape()                             */
414
/*                                                                      */
415
/*      Translate UTF8 to Win1252 and escape special characters like    */
416
/*      newline and space with DXF style escapes.  Note that            */
417
/*      non-win1252 unicode characters are translated using the         */
418
/*      unicode escape sequence.                                        */
419
/************************************************************************/
420
421
CPLString OGRDXFWriterLayer::TextEscape(const char *pszInput)
422
423
0
{
424
0
    CPLString osResult;
425
0
    wchar_t *panInput = CPLRecodeToWChar(pszInput, CPL_ENC_UTF8, CPL_ENC_UCS2);
426
0
    for (int i = 0; panInput[i] != 0; i++)
427
0
    {
428
0
        if (panInput[i] == '\n')
429
0
        {
430
0
            osResult += "\\P";
431
0
        }
432
0
        else if (panInput[i] == ' ')
433
0
        {
434
0
            osResult += "\\~";
435
0
        }
436
0
        else if (panInput[i] == '\\')
437
0
        {
438
0
            osResult += "\\\\";
439
0
        }
440
0
        else if (panInput[i] == '^')
441
0
        {
442
0
            osResult += "^ ";
443
0
        }
444
0
        else if (panInput[i] < ' ')
445
0
        {
446
0
            osResult += '^';
447
0
            osResult += static_cast<char>(panInput[i] + '@');
448
0
        }
449
0
        else if (panInput[i] > 255)
450
0
        {
451
0
            CPLString osUnicode;
452
0
            osUnicode.Printf("\\U+%04x", (int)panInput[i]);
453
0
            osResult += osUnicode;
454
0
        }
455
0
        else
456
0
        {
457
0
            osResult += (char)panInput[i];
458
0
        }
459
0
    }
460
461
0
    CPLFree(panInput);
462
463
0
    return osResult;
464
0
}
465
466
/************************************************************************/
467
/*                     PrepareTextStyleDefinition()                     */
468
/************************************************************************/
469
std::map<CPLString, CPLString>
470
OGRDXFWriterLayer::PrepareTextStyleDefinition(OGRStyleLabel *poLabelTool)
471
0
{
472
0
    GBool bDefault;
473
474
0
    std::map<CPLString, CPLString> oTextStyleDef;
475
476
    /* -------------------------------------------------------------------- */
477
    /*      Fetch the data for this text style.                             */
478
    /* -------------------------------------------------------------------- */
479
0
    const char *pszFontName = poLabelTool->FontName(bDefault);
480
0
    if (!bDefault)
481
0
        oTextStyleDef["Font"] = pszFontName;
482
483
0
    const GBool bBold = poLabelTool->Bold(bDefault);
484
0
    if (!bDefault)
485
0
        oTextStyleDef["Bold"] = bBold ? "1" : "0";
486
487
0
    const GBool bItalic = poLabelTool->Italic(bDefault);
488
0
    if (!bDefault)
489
0
        oTextStyleDef["Italic"] = bItalic ? "1" : "0";
490
491
0
    const double dfStretch = poLabelTool->Stretch(bDefault);
492
0
    if (!bDefault)
493
0
    {
494
0
        oTextStyleDef["Width"] = CPLString().Printf("%f", dfStretch / 100.0);
495
0
    }
496
497
0
    return oTextStyleDef;
498
0
}
499
500
/************************************************************************/
501
/*                             WriteTEXT()                              */
502
/************************************************************************/
503
504
OGRErr OGRDXFWriterLayer::WriteTEXT(OGRFeature *poFeature)
505
506
0
{
507
0
    CorePropertiesType oCoreProperties;
508
509
    /* -------------------------------------------------------------------- */
510
    /*      Do we have styling information?                                 */
511
    /* -------------------------------------------------------------------- */
512
0
    OGRStyleTool *poTool = nullptr;
513
0
    OGRStyleMgr oSM;
514
515
0
    if (poFeature->GetStyleString() != nullptr)
516
0
    {
517
0
        oSM.InitFromFeature(poFeature);
518
519
0
        if (oSM.GetPartCount() > 0)
520
0
            poTool = oSM.GetPart(0);
521
0
    }
522
523
0
    if (poTool && poTool->GetType() == OGRSTCLabel)
524
0
    {
525
0
        OGRStyleLabel *poLabel = cpl::down_cast<OGRStyleLabel *>(poTool);
526
0
        GBool bDefault;
527
0
        const char *pszColor = poLabel->ForeColor(bDefault);
528
0
        if (pszColor && !bDefault)
529
0
            oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
530
0
    }
531
532
0
    WriteValue(0, "MTEXT");
533
0
    WriteCore(poFeature, oCoreProperties);
534
0
    WriteValue(100, "AcDbMText");
535
536
    /* ==================================================================== */
537
    /*      Process the LABEL tool.                                         */
538
    /* ==================================================================== */
539
0
    double dfDx = 0.0;
540
0
    double dfDy = 0.0;
541
542
0
    if (poTool && poTool->GetType() == OGRSTCLabel)
543
0
    {
544
0
        OGRStyleLabel *poLabel = cpl::down_cast<OGRStyleLabel *>(poTool);
545
0
        GBool bDefault;
546
547
        /* --------------------------------------------------------------------
548
         */
549
        /*      Angle */
550
        /* --------------------------------------------------------------------
551
         */
552
0
        const double dfAngle = poLabel->Angle(bDefault);
553
554
0
        if (!bDefault)
555
0
            WriteValue(50, dfAngle);
556
557
        /* --------------------------------------------------------------------
558
         */
559
        /*      Height - We need to fetch this in georeferenced units - I'm */
560
        /*      doubt the default translation mechanism will be much good. */
561
        /* --------------------------------------------------------------------
562
         */
563
0
        poTool->SetUnit(OGRSTUGround);
564
0
        const double dfHeight = poLabel->Size(bDefault);
565
566
0
        if (!bDefault)
567
0
            WriteValue(40, dfHeight);
568
569
        /* --------------------------------------------------------------------
570
         */
571
        /*      Anchor / Attachment Point */
572
        /* --------------------------------------------------------------------
573
         */
574
0
        const int nAnchor = poLabel->Anchor(bDefault);
575
576
0
        if (!bDefault)
577
0
        {
578
0
            const static int anAnchorMap[] = {-1, 7, 8, 9, 4, 5, 6,
579
0
                                              1,  2, 3, 7, 8, 9};
580
581
0
            if (nAnchor > 0 && nAnchor < 13)
582
0
                WriteValue(71, anAnchorMap[nAnchor]);
583
0
        }
584
585
        /* --------------------------------------------------------------------
586
         */
587
        /*      Offset */
588
        /* --------------------------------------------------------------------
589
         */
590
0
        dfDx = poLabel->SpacingX(bDefault);
591
0
        dfDy = poLabel->SpacingY(bDefault);
592
593
        /* --------------------------------------------------------------------
594
         */
595
        /*      Escape the text, and convert to ISO8859. */
596
        /* --------------------------------------------------------------------
597
         */
598
0
        const char *pszText = poLabel->TextString(bDefault);
599
600
0
        if (pszText != nullptr && !bDefault)
601
0
        {
602
0
            CPLString osEscaped = TextEscape(pszText);
603
0
            while (osEscaped.size() > 250)
604
0
            {
605
0
                WriteValue(3, osEscaped.substr(0, 250).c_str());
606
0
                osEscaped.erase(0, 250);
607
0
            }
608
0
            WriteValue(1, osEscaped);
609
0
        }
610
611
        /* --------------------------------------------------------------------
612
         */
613
        /*      Store the text style in the map. */
614
        /* --------------------------------------------------------------------
615
         */
616
0
        std::map<CPLString, CPLString> oTextStyleDef =
617
0
            PrepareTextStyleDefinition(poLabel);
618
0
        CPLString osStyleName;
619
620
0
        for (const auto &oPair : oNewTextStyles)
621
0
        {
622
0
            if (oPair.second == oTextStyleDef)
623
0
            {
624
0
                osStyleName = oPair.first;
625
0
                break;
626
0
            }
627
0
        }
628
629
0
        if (osStyleName == "")
630
0
        {
631
632
0
            do
633
0
            {
634
0
                osStyleName.Printf("AutoTextStyle-%d", nNextAutoID++);
635
0
            } while (poDS->oHeaderDS.TextStyleExists(osStyleName));
636
637
0
            oNewTextStyles[osStyleName] = std::move(oTextStyleDef);
638
0
        }
639
640
0
        WriteValue(7, osStyleName);
641
0
    }
642
643
0
    delete poTool;
644
645
    /* -------------------------------------------------------------------- */
646
    /*      Write the location.                                             */
647
    /* -------------------------------------------------------------------- */
648
0
    OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
649
650
0
    WriteValue(10, poPoint->getX() + dfDx);
651
0
    if (!WriteValue(20, poPoint->getY() + dfDy))
652
0
        return OGRERR_FAILURE;
653
654
0
    if (poPoint->getGeometryType() == wkbPoint25D)
655
0
    {
656
0
        if (!WriteValue(30, poPoint->getZ()))
657
0
            return OGRERR_FAILURE;
658
0
    }
659
660
0
    return OGRERR_NONE;
661
0
}
662
663
/************************************************************************/
664
/*                     PrepareLineTypeDefinition()                      */
665
/************************************************************************/
666
std::vector<double>
667
OGRDXFWriterLayer::PrepareLineTypeDefinition(OGRStylePen *poPen)
668
0
{
669
670
    /* -------------------------------------------------------------------- */
671
    /*      Fetch pattern.                                                  */
672
    /* -------------------------------------------------------------------- */
673
0
    GBool bDefault;
674
0
    const char *pszPattern = poPen->Pattern(bDefault);
675
676
0
    if (bDefault || strlen(pszPattern) == 0)
677
0
        return std::vector<double>();
678
679
    /* -------------------------------------------------------------------- */
680
    /*      Split into pen up / pen down bits.                              */
681
    /* -------------------------------------------------------------------- */
682
0
    char **papszTokens = CSLTokenizeString(pszPattern);
683
0
    std::vector<double> adfWeightTokens;
684
685
0
    for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; i++)
686
0
    {
687
0
        const char *pszToken = papszTokens[i];
688
0
        CPLString osAmount;
689
0
        CPLString osDXFEntry;
690
691
        // Split amount and unit.
692
0
        const char *pszUnit = pszToken;  // Used after for.
693
0
        for (; strchr("0123456789.", *pszUnit) != nullptr; pszUnit++)
694
0
        {
695
0
        }
696
697
0
        osAmount.assign(pszToken, (int)(pszUnit - pszToken));
698
699
        // If the unit is other than 'g' we really should be trying to
700
        // do some type of transformation - but what to do?  Pretty hard.
701
702
        // Even entries are "pen down" represented as positive in DXF.
703
        // "Pen up" entries (gaps) are represented as negative.
704
0
        if (i % 2 == 0)
705
0
            adfWeightTokens.push_back(CPLAtof(osAmount));
706
0
        else
707
0
            adfWeightTokens.push_back(-CPLAtof(osAmount));
708
0
    }
709
710
0
    CSLDestroy(papszTokens);
711
712
0
    return adfWeightTokens;
713
0
}
714
715
/************************************************************************/
716
/*                       IsLineTypeProportional()                       */
717
/************************************************************************/
718
719
static double IsLineTypeProportional(const std::vector<double> &adfA,
720
                                     const std::vector<double> &adfB)
721
0
{
722
    // If they are not the same length, they are not the same linetype
723
0
    if (adfA.size() != adfB.size())
724
0
        return 0.0;
725
726
    // Determine the proportion of the first elements
727
0
    const double dfRatio = (adfA[0] != 0.0) ? (adfB[0] / adfA[0]) : 0.0;
728
729
    // Check if all elements follow this proportionality
730
0
    for (size_t iIndex = 1; iIndex < adfA.size(); iIndex++)
731
0
        if (fabs(adfB[iIndex] - (adfA[iIndex] * dfRatio)) > 1e-6)
732
0
            return 0.0;
733
734
0
    return dfRatio;
735
0
}
736
737
/************************************************************************/
738
/*                           WritePOLYLINE()                            */
739
/************************************************************************/
740
741
OGRErr OGRDXFWriterLayer::WritePOLYLINE(OGRFeature *poFeature,
742
                                        const OGRGeometry *poGeom)
743
744
0
{
745
    /* -------------------------------------------------------------------- */
746
    /*      For now we handle multilinestrings by writing a series of       */
747
    /*      entities.                                                       */
748
    /* -------------------------------------------------------------------- */
749
0
    if (poGeom == nullptr)
750
0
        poGeom = poFeature->GetGeometryRef();
751
752
0
    if (poGeom->IsEmpty())
753
0
    {
754
0
        return OGRERR_NONE;
755
0
    }
756
757
0
    if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon ||
758
0
        wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString)
759
0
    {
760
0
        const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
761
0
        OGRErr eErr = OGRERR_NONE;
762
0
        for (auto &&poMember : *poGC)
763
0
        {
764
0
            eErr = WritePOLYLINE(poFeature, poMember);
765
0
            if (eErr != OGRERR_NONE)
766
0
                break;
767
0
        }
768
769
0
        return eErr;
770
0
    }
771
772
    /* -------------------------------------------------------------------- */
773
    /*      Polygons are written with on entity per ring.                   */
774
    /* -------------------------------------------------------------------- */
775
0
    if (wkbFlatten(poGeom->getGeometryType()) == wkbPolygon ||
776
0
        wkbFlatten(poGeom->getGeometryType()) == wkbTriangle)
777
0
    {
778
0
        const OGRPolygon *poPoly = poGeom->toPolygon();
779
0
        OGRErr eErr = OGRERR_NONE;
780
0
        for (auto &&poRing : *poPoly)
781
0
        {
782
0
            eErr = WritePOLYLINE(poFeature, poRing);
783
0
            if (eErr != OGRERR_NONE)
784
0
                break;
785
0
        }
786
787
0
        return eErr;
788
0
    }
789
790
    /* -------------------------------------------------------------------- */
791
    /*      Do we now have a geometry we can work with?                     */
792
    /* -------------------------------------------------------------------- */
793
0
    if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
794
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
795
796
0
    const OGRLineString *poLS = poGeom->toLineString();
797
798
    /* -------------------------------------------------------------------- */
799
    /*      Write as a lightweight polygon,                                 */
800
    /*       or as POLYLINE if the line contains different heights          */
801
    /* -------------------------------------------------------------------- */
802
0
    int bHasDifferentZ = FALSE;
803
0
    if (poLS->getGeometryType() == wkbLineString25D)
804
0
    {
805
0
        double z0 = poLS->getZ(0);
806
0
        for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
807
0
        {
808
0
            if (z0 != poLS->getZ(iVert))
809
0
            {
810
0
                bHasDifferentZ = TRUE;
811
0
                break;
812
0
            }
813
0
        }
814
0
    }
815
816
0
    CorePropertiesType oCoreProperties;
817
818
    /* -------------------------------------------------------------------- */
819
    /*      Do we have styling information?                                 */
820
    /* -------------------------------------------------------------------- */
821
0
    OGRStyleTool *poTool = nullptr;
822
0
    OGRStyleMgr oSM;
823
824
0
    if (poFeature->GetStyleString() != nullptr)
825
0
    {
826
0
        oSM.InitFromFeature(poFeature);
827
828
0
        if (oSM.GetPartCount() > 0)
829
0
            poTool = oSM.GetPart(0);
830
0
    }
831
832
    /* -------------------------------------------------------------------- */
833
    /*      Handle a PEN tool to control drawing color and width.           */
834
    /* -------------------------------------------------------------------- */
835
0
    if (poTool && poTool->GetType() == OGRSTCPen)
836
0
    {
837
0
        OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
838
0
        GBool bDefault;
839
0
        const char *pszColor = poPen->Color(bDefault);
840
0
        if (pszColor && !bDefault)
841
0
            oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
842
0
    }
843
844
0
    WriteValue(0, bHasDifferentZ ? "POLYLINE" : "LWPOLYLINE");
845
0
    WriteCore(poFeature, oCoreProperties);
846
0
    if (bHasDifferentZ)
847
0
    {
848
0
        WriteValue(100, "AcDb3dPolyline");
849
0
        WriteValue(10, 0.0);
850
0
        WriteValue(20, 0.0);
851
0
        WriteValue(30, 0.0);
852
0
    }
853
0
    else
854
0
        WriteValue(100, "AcDbPolyline");
855
0
    if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
856
0
        WriteValue(70, 1 + (bHasDifferentZ ? 8 : 0));
857
0
    else
858
0
        WriteValue(70, 0 + (bHasDifferentZ ? 8 : 0));
859
0
    if (!bHasDifferentZ)
860
0
        WriteValue(90, poLS->getNumPoints());
861
0
    else
862
0
        WriteValue(66, "1");  // Vertex Flag
863
864
    /* -------------------------------------------------------------------- */
865
    /*      Handle a PEN tool to control drawing color and width.           */
866
    /*      Perhaps one day also dottedness, etc.                           */
867
    /* -------------------------------------------------------------------- */
868
0
    if (poTool && poTool->GetType() == OGRSTCPen)
869
0
    {
870
0
        OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
871
0
        GBool bDefault;
872
873
        // we want to fetch the width in ground units.
874
0
        poPen->SetUnit(OGRSTUGround, 1.0);
875
0
        const double dfWidth = poPen->Width(bDefault);
876
877
0
        if (!bDefault)
878
0
            WriteValue(370, (int)floor(dfWidth * 100 + 0.5));
879
0
    }
880
881
    /* -------------------------------------------------------------------- */
882
    /*      Do we have a Linetype for the feature?                          */
883
    /* -------------------------------------------------------------------- */
884
0
    CPLString osLineType = poFeature->GetFieldAsString("Linetype");
885
0
    double dfLineTypeScale = 0.0;
886
887
0
    bool bGotLinetype = false;
888
889
0
    if (!osLineType.empty())
890
0
    {
891
0
        std::vector<double> adfLineType =
892
0
            poDS->oHeaderDS.LookupLineType(osLineType);
893
894
0
        if (adfLineType.empty() && oNewLineTypes.count(osLineType) > 0)
895
0
            adfLineType = oNewLineTypes[osLineType];
896
897
0
        if (!adfLineType.empty())
898
0
        {
899
0
            bGotLinetype = true;
900
0
            WriteValue(6, osLineType);
901
902
            // If the given linetype is proportional to the linetype data
903
            // in the style string, then apply a linetype scale
904
0
            if (poTool != nullptr && poTool->GetType() == OGRSTCPen)
905
0
            {
906
0
                std::vector<double> adfDefinition = PrepareLineTypeDefinition(
907
0
                    static_cast<OGRStylePen *>(poTool));
908
909
0
                if (!adfDefinition.empty())
910
0
                {
911
0
                    dfLineTypeScale =
912
0
                        IsLineTypeProportional(adfLineType, adfDefinition);
913
914
0
                    if (dfLineTypeScale != 0.0 &&
915
0
                        fabs(dfLineTypeScale - 1.0) > 1e-4)
916
0
                    {
917
0
                        WriteValue(48, dfLineTypeScale);
918
0
                    }
919
0
                }
920
0
            }
921
0
        }
922
0
    }
923
924
0
    if (!bGotLinetype && poTool != nullptr && poTool->GetType() == OGRSTCPen)
925
0
    {
926
0
        std::vector<double> adfDefinition =
927
0
            PrepareLineTypeDefinition(static_cast<OGRStylePen *>(poTool));
928
929
0
        if (!adfDefinition.empty())
930
0
        {
931
            // Is this definition already created and named?
932
0
            for (const auto &oPair : poDS->oHeaderDS.GetLineTypeTable())
933
0
            {
934
0
                dfLineTypeScale =
935
0
                    IsLineTypeProportional(oPair.second, adfDefinition);
936
0
                if (dfLineTypeScale != 0.0)
937
0
                {
938
0
                    osLineType = oPair.first;
939
0
                    break;
940
0
                }
941
0
            }
942
943
0
            if (dfLineTypeScale == 0.0)
944
0
            {
945
0
                for (const auto &oPair : oNewLineTypes)
946
0
                {
947
0
                    dfLineTypeScale =
948
0
                        IsLineTypeProportional(oPair.second, adfDefinition);
949
0
                    if (dfLineTypeScale != 0.0)
950
0
                    {
951
0
                        osLineType = oPair.first;
952
0
                        break;
953
0
                    }
954
0
                }
955
0
            }
956
957
            // If not, create an automatic name for it.
958
0
            if (osLineType == "")
959
0
            {
960
0
                dfLineTypeScale = 1.0;
961
0
                do
962
0
                {
963
0
                    osLineType.Printf("AutoLineType-%d", nNextAutoID++);
964
0
                } while (poDS->oHeaderDS.LookupLineType(osLineType).size() > 0);
965
0
            }
966
967
            // If it isn't already defined, add it now.
968
0
            if (poDS->oHeaderDS.LookupLineType(osLineType).empty() &&
969
0
                oNewLineTypes.count(osLineType) == 0)
970
0
            {
971
0
                oNewLineTypes[osLineType] = std::move(adfDefinition);
972
0
            }
973
974
0
            WriteValue(6, osLineType);
975
976
0
            if (dfLineTypeScale != 0.0 && fabs(dfLineTypeScale - 1.0) > 1e-4)
977
0
            {
978
0
                WriteValue(48, dfLineTypeScale);
979
0
            }
980
0
        }
981
0
    }
982
983
    /* -------------------------------------------------------------------- */
984
    /*      Write the vertices                                              */
985
    /* -------------------------------------------------------------------- */
986
987
0
    if (!bHasDifferentZ && poLS->getGeometryType() == wkbLineString25D)
988
0
    {
989
        // if LWPOLYLINE with Z write it only once
990
0
        if (!WriteValue(38, poLS->getZ(0)))
991
0
            return OGRERR_FAILURE;
992
0
    }
993
994
0
    for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
995
0
    {
996
0
        if (bHasDifferentZ)
997
0
        {
998
0
            WriteValue(0, "VERTEX");
999
0
            WriteCore(poFeature, CorePropertiesType());
1000
0
            WriteValue(100, "AcDbVertex");
1001
0
            WriteValue(100, "AcDb3dPolylineVertex");
1002
0
        }
1003
0
        WriteValue(10, poLS->getX(iVert));
1004
0
        if (!WriteValue(20, poLS->getY(iVert)))
1005
0
            return OGRERR_FAILURE;
1006
1007
0
        if (bHasDifferentZ)
1008
0
        {
1009
0
            if (!WriteValue(30, poLS->getZ(iVert)))
1010
0
                return OGRERR_FAILURE;
1011
0
            WriteValue(70, 32);
1012
0
        }
1013
0
    }
1014
1015
0
    if (bHasDifferentZ)
1016
0
    {
1017
0
        WriteValue(0, "SEQEND");
1018
0
        WriteCore(poFeature, CorePropertiesType());
1019
0
    }
1020
1021
0
    delete poTool;
1022
1023
0
    return OGRERR_NONE;
1024
1025
#ifdef notdef
1026
    /* -------------------------------------------------------------------- */
1027
    /*      Alternate unmaintained implementation as a polyline entity.     */
1028
    /* -------------------------------------------------------------------- */
1029
    WriteValue(0, "POLYLINE");
1030
    WriteCore(poFeature);
1031
    WriteValue(100, "AcDbPolyline");
1032
    if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1033
        WriteValue(70, 1);
1034
    else
1035
        WriteValue(70, 0);
1036
    WriteValue(66, "1");
1037
1038
    for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
1039
    {
1040
        WriteValue(0, "VERTEX");
1041
        WriteValue(8, "0");
1042
        WriteValue(10, poLS->getX(iVert));
1043
        if (!WriteValue(20, poLS->getY(iVert)))
1044
            return OGRERR_FAILURE;
1045
1046
        if (poLS->getGeometryType() == wkbLineString25D)
1047
        {
1048
            if (!WriteValue(30, poLS->getZ(iVert)))
1049
                return OGRERR_FAILURE;
1050
        }
1051
    }
1052
1053
    WriteValue(0, "SEQEND");
1054
    WriteValue(8, "0");
1055
1056
    return OGRERR_NONE;
1057
#endif
1058
0
}
1059
1060
/************************************************************************/
1061
/*                             WriteHATCH()                             */
1062
/************************************************************************/
1063
1064
OGRErr OGRDXFWriterLayer::WriteHATCH(OGRFeature *poFeature, OGRGeometry *poGeom)
1065
1066
0
{
1067
    /* -------------------------------------------------------------------- */
1068
    /*      For now we handle multipolygons by writing a series of          */
1069
    /*      entities.                                                       */
1070
    /* -------------------------------------------------------------------- */
1071
0
    if (poGeom == nullptr)
1072
0
        poGeom = poFeature->GetGeometryRef();
1073
1074
0
    if (poGeom->IsEmpty())
1075
0
    {
1076
0
        return OGRERR_NONE;
1077
0
    }
1078
1079
0
    if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon)
1080
0
    {
1081
0
        OGRErr eErr = OGRERR_NONE;
1082
0
        for (auto &&poMember : poGeom->toMultiPolygon())
1083
0
        {
1084
0
            eErr = WriteHATCH(poFeature, poMember);
1085
0
            if (eErr != OGRERR_NONE)
1086
0
                break;
1087
0
        }
1088
1089
0
        return eErr;
1090
0
    }
1091
1092
    /* -------------------------------------------------------------------- */
1093
    /*      Do we now have a geometry we can work with?                     */
1094
    /* -------------------------------------------------------------------- */
1095
0
    if (wkbFlatten(poGeom->getGeometryType()) != wkbPolygon &&
1096
0
        wkbFlatten(poGeom->getGeometryType()) != wkbTriangle)
1097
0
    {
1098
0
        return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
1099
0
    }
1100
1101
0
    CorePropertiesType oCoreProperties;
1102
1103
    /* -------------------------------------------------------------------- */
1104
    /*      Do we have styling information?                                 */
1105
    /* -------------------------------------------------------------------- */
1106
0
    OGRStyleTool *poTool = nullptr;
1107
0
    OGRStyleMgr oSM;
1108
1109
0
    if (poFeature->GetStyleString() != nullptr)
1110
0
    {
1111
0
        oSM.InitFromFeature(poFeature);
1112
1113
0
        if (oSM.GetPartCount() > 0)
1114
0
            poTool = oSM.GetPart(0);
1115
0
    }
1116
    // Write style brush fore color
1117
0
    std::string osBrushId;
1118
0
    std::string osBackgroundColor;
1119
0
    double dfSize = 1.0;
1120
0
    double dfAngle = 0.0;
1121
0
    if (poTool && poTool->GetType() == OGRSTCBrush)
1122
0
    {
1123
0
        OGRStyleBrush *poBrush = cpl::down_cast<OGRStyleBrush *>(poTool);
1124
0
        GBool bDefault;
1125
1126
0
        const char *pszBrushId = poBrush->Id(bDefault);
1127
0
        if (pszBrushId && !bDefault)
1128
0
            osBrushId = pszBrushId;
1129
1130
        // null brush (transparent - no fill, irrespective of fc or bc values
1131
0
        if (osBrushId == "ogr-brush-1")
1132
0
        {
1133
0
            oCoreProperties.emplace_back(PROP_RGBA_COLOR, "#00000000");
1134
0
        }
1135
0
        else
1136
0
        {
1137
0
            const char *pszColor = poBrush->ForeColor(bDefault);
1138
0
            if (pszColor != nullptr && !bDefault)
1139
0
                oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
1140
1141
0
            const char *pszBGColor = poBrush->BackColor(bDefault);
1142
0
            if (pszBGColor != nullptr && !bDefault)
1143
0
                osBackgroundColor = pszBGColor;
1144
0
        }
1145
1146
0
        double dfStyleSize = poBrush->Size(bDefault);
1147
0
        if (!bDefault)
1148
0
            dfSize = dfStyleSize;
1149
1150
0
        double dfStyleAngle = poBrush->Angle(bDefault);
1151
0
        if (!bDefault)
1152
0
            dfAngle = dfStyleAngle;
1153
0
    }
1154
0
    delete poTool;
1155
1156
    /* -------------------------------------------------------------------- */
1157
    /*      Write as a hatch.                                               */
1158
    /* -------------------------------------------------------------------- */
1159
0
    WriteValue(0, "HATCH");
1160
0
    WriteCore(poFeature, oCoreProperties);
1161
0
    WriteValue(100, "AcDbHatch");
1162
1163
    // Figure out "average" elevation
1164
0
    OGREnvelope3D oEnv;
1165
0
    poGeom->getEnvelope(&oEnv);
1166
0
    WriteValue(10, 0);  // elevation point X = 0
1167
0
    WriteValue(20, 0);  // elevation point Y = 0
1168
    // elevation point Z = constant elevation
1169
0
    WriteValue(30, oEnv.MinZ + (oEnv.MaxZ - oEnv.MinZ) / 2);
1170
1171
0
    WriteValue(210, 0);    // extrusion direction X
1172
0
    WriteValue(220, 0);    // extrusion direction Y
1173
0
    WriteValue(230, 1.0);  // extrusion direction Z
1174
1175
0
    const char *pszPatternName = "SOLID";
1176
0
    double dfPatternRotation = 0;
1177
1178
    // Cf https://ezdxf.readthedocs.io/en/stable/tutorials/hatch.html#predefined-hatch-pattern
1179
    // for DXF standard hatch pattern names
1180
1181
0
    if (osBrushId.empty() || osBrushId == "ogr-brush-0")
1182
0
    {
1183
        // solid fill pattern
1184
0
    }
1185
0
    else if (osBrushId == "ogr-brush-2")
1186
0
    {
1187
        // horizontal line.
1188
0
        pszPatternName = "ANSI31";
1189
0
        dfPatternRotation = -45;
1190
0
    }
1191
0
    else if (osBrushId == "ogr-brush-3")
1192
0
    {
1193
        // vertical line.
1194
0
        pszPatternName = "ANSI31";
1195
0
        dfPatternRotation = 45;
1196
0
    }
1197
0
    else if (osBrushId == "ogr-brush-4")
1198
0
    {
1199
        // top-left to bottom-right diagonal hatch.
1200
0
        pszPatternName = "ANSI31";
1201
0
        dfPatternRotation = 90;
1202
0
    }
1203
0
    else if (osBrushId == "ogr-brush-5")
1204
0
    {
1205
        // bottom-left to top-right diagonal hatch
1206
0
        pszPatternName = "ANSI31";
1207
0
        dfPatternRotation = 0;
1208
0
    }
1209
0
    else if (osBrushId == "ogr-brush-6")
1210
0
    {
1211
        // cross hatch
1212
0
        pszPatternName = "ANSI37";
1213
0
        dfPatternRotation = 45;
1214
0
    }
1215
0
    else if (osBrushId == "ogr-brush-7")
1216
0
    {
1217
        // diagonal cross hatch
1218
0
        pszPatternName = "ANSI37";
1219
0
        dfPatternRotation = 0;
1220
0
    }
1221
0
    else
1222
0
    {
1223
        // solid fill pattern as a fallback
1224
0
    }
1225
1226
0
    dfPatternRotation += dfAngle;
1227
1228
0
    WriteValue(2, pszPatternName);
1229
1230
0
    if (EQUAL(pszPatternName, "ANSI31") || EQUAL(pszPatternName, "ANSI37"))
1231
0
    {
1232
0
        WriteValue(70, 0);  // pattern fill
1233
0
    }
1234
0
    else
1235
0
    {
1236
0
        WriteValue(70, 1);  // solid fill
1237
0
    }
1238
0
    WriteValue(71, 0);  // associativity
1239
1240
/* -------------------------------------------------------------------- */
1241
/*      Handle a PEN tool to control drawing color and width.           */
1242
/*      Perhaps one day also dottedness, etc.                           */
1243
/* -------------------------------------------------------------------- */
1244
#ifdef notdef
1245
    if (poTool && poTool->GetType() == OGRSTCPen)
1246
    {
1247
        OGRStylePen *poPen = (OGRStylePen *)poTool;
1248
        GBool bDefault;
1249
1250
        if (poPen->Color(bDefault) != NULL && !bDefault)
1251
            WriteValue(62, ColorStringToDXFColor(poPen->Color(bDefault)));
1252
1253
        double dfWidthInMM = poPen->Width(bDefault);
1254
1255
        if (!bDefault)
1256
            WriteValue(370, (int)floor(dfWidthInMM * 100 + 0.5));
1257
    }
1258
1259
    /* -------------------------------------------------------------------- */
1260
    /*      Do we have a Linetype for the feature?                          */
1261
    /* -------------------------------------------------------------------- */
1262
    CPLString osLineType = poFeature->GetFieldAsString("Linetype");
1263
1264
    if (!osLineType.empty() &&
1265
        (poDS->oHeaderDS.LookupLineType(osLineType) != NULL ||
1266
         oNewLineTypes.count(osLineType) > 0))
1267
    {
1268
        // Already define -> just reference it.
1269
        WriteValue(6, osLineType);
1270
    }
1271
    else if (poTool != NULL && poTool->GetType() == OGRSTCPen)
1272
    {
1273
        CPLString osDefinition = PrepareLineTypeDefinition(poFeature, poTool);
1274
1275
        if (osDefinition != "" && osLineType == "")
1276
        {
1277
            // Is this definition already created and named?
1278
            std::map<CPLString, CPLString>::iterator it;
1279
1280
            for (it = oNewLineTypes.begin(); it != oNewLineTypes.end(); it++)
1281
            {
1282
                if ((*it).second == osDefinition)
1283
                {
1284
                    osLineType = (*it).first;
1285
                    break;
1286
                }
1287
            }
1288
1289
            // create an automatic name for it.
1290
            if (osLineType == "")
1291
            {
1292
                do
1293
                {
1294
                    osLineType.Printf("AutoLineType-%d", nNextAutoID++);
1295
                } while (poDS->oHeaderDS.LookupLineType(osLineType) != NULL);
1296
            }
1297
        }
1298
1299
        // If it isn't already defined, add it now.
1300
        if (osDefinition != "" && oNewLineTypes.count(osLineType) == 0)
1301
        {
1302
            oNewLineTypes[osLineType] = osDefinition;
1303
            WriteValue(6, osLineType);
1304
        }
1305
    }
1306
    delete poTool;
1307
#endif
1308
1309
    /* -------------------------------------------------------------------- */
1310
    /*      Process the loops (rings).                                      */
1311
    /* -------------------------------------------------------------------- */
1312
0
    const OGRPolygon *poPoly = poGeom->toPolygon();
1313
1314
0
    WriteValue(91, poPoly->getNumInteriorRings() + 1);
1315
1316
0
    for (auto &&poLR : *poPoly)
1317
0
    {
1318
0
        WriteValue(92, 2);  // Polyline
1319
0
        WriteValue(72, 0);  // has bulge
1320
0
        WriteValue(73, 1);  // is closed
1321
0
        WriteValue(93, poLR->getNumPoints());
1322
1323
0
        for (int iVert = 0; iVert < poLR->getNumPoints(); iVert++)
1324
0
        {
1325
0
            WriteValue(10, poLR->getX(iVert));
1326
0
            WriteValue(20, poLR->getY(iVert));
1327
0
        }
1328
1329
0
        WriteValue(97, 0);  // 0 source boundary objects
1330
0
    }
1331
1332
0
    WriteValue(75, 0);  // hatch style = Hatch "odd parity" area (Normal style)
1333
0
    WriteValue(76, 1);  // hatch pattern type = predefined
1334
1335
0
    const auto roundIfClose = [](double x)
1336
0
    {
1337
0
        if (std::fabs(x - std::round(x)) < 1e-12)
1338
0
            x = std::round(x);
1339
0
        return x == 0 ? 0 : x;  // make sure we return positive zero
1340
0
    };
1341
1342
0
    if (EQUAL(pszPatternName, "ANSI31"))
1343
0
    {
1344
        // Single line. With dfPatternRotation=0, this is a bottom-left to top-right diagonal hatch
1345
1346
0
        WriteValue(52, dfPatternRotation);  // Hatch pattern angle
1347
0
        WriteValue(41, dfSize);             // Hatch pattern scale or spacing
1348
0
        WriteValue(77, 0);  // Hatch pattern double flag : 0 = not double
1349
1350
0
        WriteValue(78, 1);  // Number of pattern definition lines
1351
1352
0
        const double angle = dfPatternRotation + 45.0;
1353
0
        WriteValue(53, angle);  // Pattern line angle
1354
0
        WriteValue(43, 0.0);    // Pattern line base point, X component
1355
0
        WriteValue(44, 0.0);    // Pattern line base point, Y component
1356
0
        WriteValue(45, dfSize * 3.175 *
1357
0
                           roundIfClose(
1358
0
                               cos((angle + 90.0) / 180 *
1359
0
                                   M_PI)));  // Pattern line offset, X component
1360
0
        WriteValue(46, dfSize * 3.175 *
1361
0
                           roundIfClose(
1362
0
                               sin((angle + 90.0) / 180 *
1363
0
                                   M_PI)));  // Pattern line offset, Y component
1364
0
        WriteValue(79, 0);                   // Number of dash items
1365
0
    }
1366
0
    else if (EQUAL(pszPatternName, "ANSI37"))
1367
0
    {
1368
        // cross hatch. With dfPatternRotation=0, lines are diagonals
1369
1370
0
        WriteValue(52, dfPatternRotation);  // Hatch pattern angle
1371
0
        WriteValue(41, dfSize);             // Hatch pattern scale or spacing
1372
0
        WriteValue(77, 0);  // Hatch pattern double flag : 0 = not double
1373
1374
0
        WriteValue(78, 2);  // Number of pattern definition lines
1375
1376
0
        const double angle1 = dfPatternRotation + 45;
1377
0
        WriteValue(53, angle1);  // Pattern line angle
1378
0
        WriteValue(43, 0.0);     // Pattern line base point, X component
1379
0
        WriteValue(44, 0.0);     // Pattern line base point, Y component
1380
0
        WriteValue(45, dfSize * 3.175 *
1381
0
                           roundIfClose(
1382
0
                               cos((angle1 + 90.0) / 180 *
1383
0
                                   M_PI)));  // Pattern line offset, X component
1384
0
        WriteValue(46, dfSize * 3.175 *
1385
0
                           roundIfClose(
1386
0
                               sin((angle1 + 90.0) / 180 *
1387
0
                                   M_PI)));  // Pattern line offset, Y component
1388
0
        WriteValue(79, 0);                   // Number of dash items
1389
1390
0
        const double angle2 = dfPatternRotation + 135;
1391
0
        WriteValue(53, angle2);  // Pattern line angle
1392
0
        WriteValue(43, 0.0);     // Pattern line base point, X component
1393
0
        WriteValue(44, 0.0);     // Pattern line base point, Y component
1394
0
        WriteValue(45, dfSize * 3.175 *
1395
0
                           roundIfClose(
1396
0
                               cos((angle2 + 90.0) / 180 *
1397
0
                                   M_PI)));  // Pattern line offset, X component
1398
0
        WriteValue(46, dfSize * 3.175 *
1399
0
                           roundIfClose(
1400
0
                               sin((angle2 + 90.0) / 180 *
1401
0
                                   M_PI)));  // Pattern line offset, Y component
1402
0
        WriteValue(79, 0);                   // Number of dash items
1403
0
    }
1404
1405
0
    WriteValue(98, 0);  // 0 seed points
1406
1407
    // Deal with brush background color
1408
0
    if (!osBackgroundColor.empty())
1409
0
    {
1410
0
        bool bPerfectMatch = false;
1411
0
        int nColor =
1412
0
            ColorStringToDXFColor(osBackgroundColor.c_str(), bPerfectMatch);
1413
0
        if (nColor >= 0)
1414
0
        {
1415
0
            WriteValue(1001, "HATCHBACKGROUNDCOLOR");
1416
0
            if (bPerfectMatch)
1417
0
            {
1418
                // C3 is top 8 bit means an indexed color
1419
0
                unsigned nRGBColorUnsigned =
1420
0
                    (static_cast<unsigned>(0xC3) << 24) |
1421
0
                    ((nColor & 0xff) << 0);
1422
                // Convert to signed (negative) value
1423
0
                int nRGBColorSigned;
1424
0
                memcpy(&nRGBColorSigned, &nRGBColorUnsigned,
1425
0
                       sizeof(nRGBColorSigned));
1426
0
                WriteValue(1071, nRGBColorSigned);
1427
0
            }
1428
0
            else
1429
0
            {
1430
0
                unsigned int nRed = 0;
1431
0
                unsigned int nGreen = 0;
1432
0
                unsigned int nBlue = 0;
1433
0
                unsigned int nOpacity = 255;
1434
1435
0
                const int nCount =
1436
0
                    sscanf(osBackgroundColor.c_str(), "#%2x%2x%2x%2x", &nRed,
1437
0
                           &nGreen, &nBlue, &nOpacity);
1438
0
                if (nCount >= 3)
1439
0
                {
1440
                    // C2 is top 8 bit means a true color
1441
0
                    unsigned nRGBColorUnsigned =
1442
0
                        (static_cast<unsigned>(0xC2) << 24) |
1443
0
                        ((nRed & 0xff) << 16) | ((nGreen & 0xff) << 8) |
1444
0
                        ((nBlue & 0xff) << 0);
1445
                    // Convert to signed (negative) value
1446
0
                    int nRGBColorSigned;
1447
0
                    memcpy(&nRGBColorSigned, &nRGBColorUnsigned,
1448
0
                           sizeof(nRGBColorSigned));
1449
0
                    WriteValue(1071, nRGBColorSigned);
1450
0
                }
1451
0
            }
1452
0
        }
1453
0
    }
1454
1455
0
    return OGRERR_NONE;
1456
1457
#ifdef notdef
1458
    /* -------------------------------------------------------------------- */
1459
    /*      Alternate unmaintained implementation as a polyline entity.     */
1460
    /* -------------------------------------------------------------------- */
1461
    WriteValue(0, "POLYLINE");
1462
    WriteCore(poFeature);
1463
    WriteValue(100, "AcDbPolyline");
1464
    if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
1465
        WriteValue(70, 1);
1466
    else
1467
        WriteValue(70, 0);
1468
    WriteValue(66, "1");
1469
1470
    for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
1471
    {
1472
        WriteValue(0, "VERTEX");
1473
        WriteValue(8, "0");
1474
        WriteValue(10, poLS->getX(iVert));
1475
        if (!WriteValue(20, poLS->getY(iVert)))
1476
            return OGRERR_FAILURE;
1477
1478
        if (poLS->getGeometryType() == wkbLineString25D)
1479
        {
1480
            if (!WriteValue(30, poLS->getZ(iVert)))
1481
                return OGRERR_FAILURE;
1482
        }
1483
    }
1484
1485
    WriteValue(0, "SEQEND");
1486
    WriteValue(8, "0");
1487
1488
    return OGRERR_NONE;
1489
#endif
1490
0
}
1491
1492
/************************************************************************/
1493
/*                           ICreateFeature()                           */
1494
/************************************************************************/
1495
1496
OGRErr OGRDXFWriterLayer::ICreateFeature(OGRFeature *poFeature)
1497
1498
0
{
1499
0
    OGRGeometry *poGeom = poFeature->GetGeometryRef();
1500
0
    OGRwkbGeometryType eGType = wkbNone;
1501
1502
0
    if (poGeom != nullptr)
1503
0
    {
1504
0
        if (!poGeom->IsEmpty())
1505
0
        {
1506
0
            OGREnvelope sEnvelope;
1507
0
            poGeom->getEnvelope(&sEnvelope);
1508
0
            poDS->UpdateExtent(&sEnvelope);
1509
0
        }
1510
0
        eGType = wkbFlatten(poGeom->getGeometryType());
1511
0
    }
1512
1513
0
    if (eGType == wkbPoint)
1514
0
    {
1515
0
        const char *pszBlockName = poFeature->GetFieldAsString("BlockName");
1516
1517
        // We don't want to treat as a blocks ref if the block is not defined
1518
0
        if (pszBlockName &&
1519
0
            poDS->oHeaderDS.LookupBlock(pszBlockName) == nullptr)
1520
0
        {
1521
0
            if (poDS->poBlocksLayer == nullptr ||
1522
0
                poDS->poBlocksLayer->FindBlock(pszBlockName) == nullptr)
1523
0
                pszBlockName = nullptr;
1524
0
        }
1525
1526
0
        if (pszBlockName != nullptr)
1527
0
            return WriteINSERT(poFeature);
1528
1529
0
        else if (poFeature->GetStyleString() != nullptr &&
1530
0
                 STARTS_WITH_CI(poFeature->GetStyleString(), "LABEL"))
1531
0
            return WriteTEXT(poFeature);
1532
0
        else
1533
0
            return WritePOINT(poFeature);
1534
0
    }
1535
0
    else if (eGType == wkbLineString || eGType == wkbMultiLineString)
1536
0
        return WritePOLYLINE(poFeature);
1537
1538
0
    else if (eGType == wkbPolygon || eGType == wkbTriangle ||
1539
0
             eGType == wkbMultiPolygon)
1540
0
    {
1541
0
        if (bWriteHatch)
1542
0
            return WriteHATCH(poFeature);
1543
0
        else
1544
0
            return WritePOLYLINE(poFeature);
1545
0
    }
1546
1547
    // Explode geometry collections into multiple entities.
1548
0
    else if (eGType == wkbGeometryCollection || eGType == wkbMultiPoint)
1549
0
    {
1550
0
        OGRGeometryCollection *poGC =
1551
0
            poFeature->StealGeometry()->toGeometryCollection();
1552
0
        for (auto &&poMember : poGC)
1553
0
        {
1554
0
            poFeature->SetGeometry(poMember);
1555
1556
0
            OGRErr eErr = CreateFeature(poFeature);
1557
1558
0
            if (eErr != OGRERR_NONE)
1559
0
            {
1560
0
                delete poGC;
1561
0
                return eErr;
1562
0
            }
1563
0
        }
1564
1565
0
        poFeature->SetGeometryDirectly(poGC);
1566
0
        return OGRERR_NONE;
1567
0
    }
1568
0
    else
1569
0
    {
1570
0
        CPLError(CE_Failure, CPLE_AppDefined,
1571
0
                 "No known way to write feature with geometry '%s'.",
1572
0
                 OGRGeometryTypeToName(eGType));
1573
0
        return OGRERR_FAILURE;
1574
0
    }
1575
0
}
1576
1577
/************************************************************************/
1578
/*                       ColorStringToDXFColor()                        */
1579
/************************************************************************/
1580
1581
int OGRDXFWriterLayer::ColorStringToDXFColor(const char *pszRGB,
1582
                                             bool &bPerfectMatch)
1583
1584
0
{
1585
0
    bPerfectMatch = false;
1586
1587
    /* -------------------------------------------------------------------- */
1588
    /*      Parse the RGB string.                                           */
1589
    /* -------------------------------------------------------------------- */
1590
0
    if (pszRGB == nullptr)
1591
0
        return -1;
1592
1593
0
    unsigned int nRed = 0;
1594
0
    unsigned int nGreen = 0;
1595
0
    unsigned int nBlue = 0;
1596
0
    unsigned int nOpacity = 255;
1597
1598
0
    const int nCount =
1599
0
        sscanf(pszRGB, "#%2x%2x%2x%2x", &nRed, &nGreen, &nBlue, &nOpacity);
1600
1601
0
    if (nCount < 3)
1602
0
        return -1;
1603
1604
    /* -------------------------------------------------------------------- */
1605
    /*      Find near color in DXF palette.                                 */
1606
    /* -------------------------------------------------------------------- */
1607
0
    const unsigned char *pabyDXFColors = ACGetColorTable();
1608
0
    int nMinDist = 768;
1609
0
    int nBestColor = -1;
1610
1611
0
    for (int i = 1; i < 256; i++)
1612
0
    {
1613
0
        const int nDist =
1614
0
            std::abs(static_cast<int>(nRed) - pabyDXFColors[i * 3 + 0]) +
1615
0
            std::abs(static_cast<int>(nGreen) - pabyDXFColors[i * 3 + 1]) +
1616
0
            std::abs(static_cast<int>(nBlue) - pabyDXFColors[i * 3 + 2]);
1617
1618
0
        if (nDist < nMinDist)
1619
0
        {
1620
0
            nBestColor = i;
1621
0
            nMinDist = nDist;
1622
0
            if (nMinDist == 0)
1623
0
            {
1624
0
                bPerfectMatch = true;
1625
0
                break;
1626
0
            }
1627
0
        }
1628
0
    }
1629
1630
0
    return nBestColor;
1631
0
}
1632
1633
/************************************************************************/
1634
/*                             GetDataset()                             */
1635
/************************************************************************/
1636
1637
GDALDataset *OGRDXFWriterLayer::GetDataset()
1638
0
{
1639
0
    return poDS;
1640
0
}