Coverage Report

Created: 2025-08-11 09:23

/src/gdal/ogr/ogrsf_frmts/dxf/ogrdxf_leader.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  DXF Translator
4
 * Purpose:  Implements translation support for LEADER and MULTILEADER
5
 *           elements as a part of the OGRDXFLayer class.
6
 * Author:   Alan Thomas, alant@outlook.com.au
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogr_dxf.h"
15
#include "cpl_conv.h"
16
#include "../../../alg/gdallinearsystem.h"
17
#include <stdexcept>
18
#include <algorithm>
19
20
static void InterpolateSpline(OGRLineString *const poLine,
21
                              const DXFTriple &oEndTangentDirection);
22
23
/************************************************************************/
24
/*                             PointDist()                              */
25
/************************************************************************/
26
27
#ifndef PointDist_defined
28
#define PointDist_defined
29
30
inline static double PointDist(double x1, double y1, double x2, double y2)
31
238k
{
32
238k
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
33
238k
}
34
#endif
35
36
inline static double PointDist(double x1, double y1, double z1, double x2,
37
                               double y2, double z2)
38
520k
{
39
520k
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) +
40
520k
                (z2 - z1) * (z2 - z1));
41
520k
}
42
43
/************************************************************************/
44
/*                          TranslateLEADER()                           */
45
/************************************************************************/
46
47
OGRDXFFeature *OGRDXFLayer::TranslateLEADER()
48
49
80.7k
{
50
80.7k
    char szLineBuf[257];
51
80.7k
    int nCode;
52
80.7k
    OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
53
54
80.7k
    OGRLineString *poLine = new OGRLineString();
55
80.7k
    bool bHaveX = false;
56
80.7k
    bool bHaveY = false;
57
80.7k
    bool bHaveZ = false;
58
80.7k
    double dfCurrentX = 0.0;
59
80.7k
    double dfCurrentY = 0.0;
60
80.7k
    double dfCurrentZ = 0.0;
61
80.7k
    int nNumVertices = 0;
62
63
80.7k
    bool bHorizontalDirectionFlip = true;
64
80.7k
    double dfHorizontalDirectionX = 1.0;
65
80.7k
    double dfHorizontalDirectionY = 0.0;
66
80.7k
    double dfHorizontalDirectionZ = 0.0;
67
80.7k
    bool bHasTextAnnotation = false;
68
80.7k
    double dfTextAnnotationWidth = 0.0;
69
80.7k
    bool bIsSpline = false;
70
71
    // spec is silent as to default, but AutoCAD assumes true
72
80.7k
    bool bWantArrowhead = true;
73
74
80.7k
    bool bReadyForDimstyleOverride = false;
75
76
80.7k
    std::map<CPLString, CPLString> oDimStyleProperties;
77
80.7k
    poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
78
79
1.96M
    while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
80
1.88M
    {
81
1.88M
        switch (nCode)
82
1.88M
        {
83
86.1k
            case 3:
84
                // 3 is the dimension style name. We don't need to store it,
85
                // let's just fetch the dimension style properties
86
86.1k
                poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
87
86.1k
                break;
88
89
285k
            case 10:
90
                // add the previous point onto the linestring
91
285k
                if (bHaveX && bHaveY && bHaveZ)
92
50.4k
                {
93
50.4k
                    poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
94
50.4k
                                     dfCurrentZ);
95
50.4k
                    bHaveY = bHaveZ = false;
96
50.4k
                }
97
285k
                dfCurrentX = CPLAtof(szLineBuf);
98
285k
                bHaveX = true;
99
285k
                break;
100
101
238k
            case 20:
102
                // add the previous point onto the linestring
103
238k
                if (bHaveX && bHaveY && bHaveZ)
104
16.3k
                {
105
16.3k
                    poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
106
16.3k
                                     dfCurrentZ);
107
16.3k
                    bHaveX = bHaveZ = false;
108
16.3k
                }
109
238k
                dfCurrentY = CPLAtof(szLineBuf);
110
238k
                bHaveY = true;
111
238k
                break;
112
113
213k
            case 30:
114
                // add the previous point onto the linestring
115
213k
                if (bHaveX && bHaveY && bHaveZ)
116
37.8k
                {
117
37.8k
                    poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
118
37.8k
                                     dfCurrentZ);
119
37.8k
                    bHaveX = bHaveY = false;
120
37.8k
                }
121
213k
                dfCurrentZ = CPLAtof(szLineBuf);
122
213k
                bHaveZ = true;
123
213k
                break;
124
125
3.56k
            case 41:
126
3.56k
                dfTextAnnotationWidth = CPLAtof(szLineBuf);
127
3.56k
                break;
128
129
6.18k
            case 71:
130
6.18k
                bWantArrowhead = atoi(szLineBuf) != 0;
131
6.18k
                break;
132
133
91.3k
            case 72:
134
91.3k
                bIsSpline = atoi(szLineBuf) != 0;
135
91.3k
                break;
136
137
12.4k
            case 73:
138
12.4k
                bHasTextAnnotation = atoi(szLineBuf) == 0;
139
12.4k
                break;
140
141
9.79k
            case 74:
142
                // DXF spec seems to have this backwards. A value of 0 actually
143
                // indicates no flipping occurs, and 1 (flip) is the default
144
9.79k
                bHorizontalDirectionFlip = atoi(szLineBuf) != 0;
145
9.79k
                break;
146
147
5.61k
            case 211:
148
5.61k
                dfHorizontalDirectionX = CPLAtof(szLineBuf);
149
5.61k
                break;
150
151
337
            case 221:
152
337
                dfHorizontalDirectionY = CPLAtof(szLineBuf);
153
337
                break;
154
155
4.94k
            case 231:
156
4.94k
                dfHorizontalDirectionZ = CPLAtof(szLineBuf);
157
4.94k
                break;
158
159
8.22k
            case 1001:
160
8.22k
                bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
161
8.22k
                break;
162
163
8.86k
            case 1070:
164
8.86k
                if (bReadyForDimstyleOverride)
165
7.30k
                {
166
                    // Store DIMSTYLE override values in the dimension
167
                    // style property map. The nInnerCode values match the
168
                    // group codes used in the DIMSTYLE table.
169
7.30k
                    const int nInnerCode = atoi(szLineBuf);
170
7.30k
                    const char *pszProperty =
171
7.30k
                        ACGetDimStylePropertyName(nInnerCode);
172
7.30k
                    if (pszProperty)
173
6.57k
                    {
174
6.57k
                        nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
175
6.57k
                        if (nCode == 1005 || nCode == 1040 || nCode == 1070)
176
4.88k
                            oDimStyleProperties[pszProperty] = szLineBuf;
177
6.57k
                    }
178
7.30k
                }
179
8.86k
                break;
180
181
910k
            default:
182
910k
                TranslateGenericProperty(poFeature, nCode, szLineBuf);
183
910k
                break;
184
1.88M
        }
185
1.88M
    }
186
187
80.7k
    if (nCode == 0)
188
78.7k
        poDS->UnreadValue();
189
190
80.7k
    if (bHaveX && bHaveY && bHaveZ)
191
15.2k
        poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY, dfCurrentZ);
192
193
    // Unpack the dimension style
194
80.7k
    bool bWantExtension = atoi(oDimStyleProperties["DIMTAD"]) > 0;
195
80.7k
    double dfTextOffset = CPLAtof(oDimStyleProperties["DIMGAP"]);
196
80.7k
    double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
197
80.7k
    double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
198
80.7k
    int nLeaderColor = atoi(oDimStyleProperties["DIMCLRD"]);
199
    // DIMLDRBLK is the entity handle of the BLOCK_RECORD table entry that
200
    // corresponds to the arrowhead block.
201
80.7k
    CPLString osArrowheadBlockHandle = oDimStyleProperties["DIMLDRBLK"];
202
203
    // Zero scale has a special meaning which we aren't interested in,
204
    // so we can change it to 1.0
205
80.7k
    if (dfScale == 0.0)
206
367
        dfScale = 1.0;
207
208
    // Use the color from the dimension style if it is not ByBlock
209
80.7k
    if (nLeaderColor > 0)
210
991
        poFeature->oStyleProperties["Color"] = oDimStyleProperties["DIMCLRD"];
211
212
    /* -------------------------------------------------------------------- */
213
    /*      Add an arrowhead to the start of the leader line.               */
214
    /* -------------------------------------------------------------------- */
215
216
80.7k
    if (bWantArrowhead && nNumVertices >= 2)
217
26.8k
    {
218
26.8k
        InsertArrowhead(poFeature, osArrowheadBlockHandle, poLine,
219
26.8k
                        dfArrowheadSize * dfScale);
220
26.8k
    }
221
222
80.7k
    if (bHorizontalDirectionFlip)
223
78.6k
    {
224
78.6k
        dfHorizontalDirectionX *= -1;
225
78.6k
        dfHorizontalDirectionX *= -1;
226
78.6k
        dfHorizontalDirectionX *= -1;
227
78.6k
    }
228
229
    /* -------------------------------------------------------------------- */
230
    /*      For a spline leader, determine the end tangent direction        */
231
    /*      and interpolate the spline vertices.                            */
232
    /* -------------------------------------------------------------------- */
233
234
80.7k
    if (bIsSpline)
235
42.0k
    {
236
42.0k
        DXFTriple oEndTangent;
237
42.0k
        if (bHasTextAnnotation)
238
5.47k
        {
239
5.47k
            oEndTangent =
240
5.47k
                DXFTriple(dfHorizontalDirectionX, dfHorizontalDirectionY,
241
5.47k
                          dfHorizontalDirectionZ);
242
5.47k
        }
243
42.0k
        InterpolateSpline(poLine, oEndTangent);
244
42.0k
    }
245
246
    /* -------------------------------------------------------------------- */
247
    /*      Add an extension to the end of the leader line. This is not     */
248
    /*      properly documented in the DXF spec, but it is needed to        */
249
    /*      replicate the way AutoCAD displays leader objects.              */
250
    /*                                                                      */
251
    /*      When $DIMTAD (77) is nonzero, the leader line is extended       */
252
    /*      under the text annotation. This extension is not stored as an   */
253
    /*      additional vertex, so we need to create it ourselves.           */
254
    /* -------------------------------------------------------------------- */
255
256
80.7k
    if (bWantExtension && bHasTextAnnotation && poLine->getNumPoints() >= 2)
257
41
    {
258
41
        OGRPoint oLastVertex;
259
41
        poLine->getPoint(poLine->getNumPoints() - 1, &oLastVertex);
260
261
41
        double dfExtensionX = oLastVertex.getX();
262
41
        double dfExtensionY = oLastVertex.getY();
263
41
        double dfExtensionZ = oLastVertex.getZ();
264
265
41
        double dfExtensionLength =
266
41
            (dfTextOffset * dfScale) + dfTextAnnotationWidth;
267
41
        dfExtensionX += dfHorizontalDirectionX * dfExtensionLength;
268
41
        dfExtensionY += dfHorizontalDirectionY * dfExtensionLength;
269
41
        dfExtensionZ += dfHorizontalDirectionZ * dfExtensionLength;
270
271
41
        poLine->setPoint(poLine->getNumPoints(), dfExtensionX, dfExtensionY,
272
41
                         dfExtensionZ);
273
41
    }
274
275
80.7k
    poFeature->SetGeometryDirectly(poLine);
276
277
80.7k
    PrepareLineStyle(poFeature);
278
279
80.7k
    return poFeature;
280
80.7k
}
281
282
/************************************************************************/
283
/*       DXFMLEADERVertex, DXFMLEADERLeaderLine, DXFMLEADERLeader       */
284
/************************************************************************/
285
286
struct DXFMLEADERVertex
287
{
288
    DXFTriple oCoords;
289
    std::vector<std::pair<DXFTriple, DXFTriple>> aoBreaks;
290
291
1.05M
    DXFMLEADERVertex(double dfX, double dfY) : oCoords(DXFTriple(dfX, dfY, 0.0))
292
1.05M
    {
293
1.05M
    }
294
};
295
296
struct DXFMLEADERLeader
297
{
298
    double dfLandingX = 0;
299
    double dfLandingY = 0;
300
    double dfDoglegVectorX = 0;
301
    double dfDoglegVectorY = 0;
302
    double dfDoglegLength = 0;
303
    std::vector<std::pair<DXFTriple, DXFTriple>> aoDoglegBreaks;
304
    std::vector<std::vector<DXFMLEADERVertex>> aaoLeaderLines;
305
};
306
307
/************************************************************************/
308
/*                         TranslateMLEADER()                           */
309
/************************************************************************/
310
311
OGRDXFFeature *OGRDXFLayer::TranslateMLEADER()
312
313
99.5k
{
314
    // The MLEADER line buffer has to be very large, as the text contents
315
    // (group code 304) do not wrap and may be arbitrarily long
316
99.5k
    char szLineBuf[4096];
317
99.5k
    int nCode = 0;
318
319
    // This is a dummy feature object used to store style properties
320
    // and the like. We end up deleting it without returning it
321
99.5k
    OGRDXFFeature *poOverallFeature = new OGRDXFFeature(poFeatureDefn);
322
323
99.5k
    DXFMLEADERLeader oLeader;
324
99.5k
    std::vector<DXFMLEADERLeader> aoLeaders;
325
326
99.5k
    std::vector<DXFMLEADERVertex> oLeaderLine;
327
99.5k
    double dfCurrentX = 0.0;
328
99.5k
    double dfCurrentY = 0.0;
329
99.5k
    double dfCurrentX2 = 0.0;
330
99.5k
    double dfCurrentY2 = 0.0;
331
99.5k
    size_t nCurrentVertex = 0;
332
333
99.5k
    double dfScale = 1.0;
334
99.5k
    bool bHasDogleg = true;
335
99.5k
    CPLString osLeaderColor = "0";
336
337
99.5k
    CPLString osText;
338
99.5k
    CPLString osTextStyleHandle;
339
99.5k
    double dfTextX = 0.0;
340
99.5k
    double dfTextY = 0.0;
341
99.5k
    int nTextAlignment = 1;  // 1 = left, 2 = center, 3 = right
342
99.5k
    double dfTextAngle = 0.0;
343
99.5k
    double dfTextHeight = 4.0;
344
345
99.5k
    CPLString osBlockHandle;
346
99.5k
    OGRDXFInsertTransformer oBlockTransformer;
347
99.5k
    CPLString osBlockAttributeHandle;
348
    // Map of ATTDEF handles to attribute text
349
99.5k
    std::map<CPLString, CPLString> oBlockAttributes;
350
351
99.5k
    CPLString osArrowheadBlockHandle;
352
99.5k
    double dfArrowheadSize = 4.0;
353
354
    // The different leader line types
355
99.5k
    const int MLT_NONE = 0;
356
99.5k
    const int MLT_STRAIGHT = 1;
357
99.5k
    const int MLT_SPLINE = 2;
358
99.5k
    int nLeaderLineType = MLT_STRAIGHT;
359
360
    // Group codes mean different things in different sections of the
361
    // MLEADER entity. We need to keep track of the section we are in.
362
99.5k
    const int MLS_COMMON = 0;
363
99.5k
    const int MLS_CONTEXT_DATA = 1;
364
99.5k
    const int MLS_LEADER = 2;
365
99.5k
    const int MLS_LEADER_LINE = 3;
366
99.5k
    int nSection = MLS_COMMON;
367
368
    // The way the 30x group codes work is missing from the DXF docs.
369
    // We assume that the sections are always nested as follows:
370
371
    // ... [this part is identified as MLS_COMMON]
372
    // 300 CONTEXT_DATA{
373
    //   ...
374
    //   302 LEADER{
375
    //     ...
376
    //     304 LEADER_LINE{
377
    //       ...
378
    //     305 }
379
    //     304 LEADER_LINE{
380
    //       ...
381
    //     305 }
382
    //     ...
383
    //   303 }
384
    //   302 LEADER{
385
    //     ...
386
    //   303 }
387
    //   ...
388
    // 301 }
389
    // ... [MLS_COMMON]
390
391
3.28M
    while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
392
3.18M
    {
393
3.18M
        switch (nSection)
394
3.18M
        {
395
551k
            case MLS_COMMON:
396
551k
                switch (nCode)
397
551k
                {
398
49.2k
                    case 300:
399
49.2k
                        nSection = MLS_CONTEXT_DATA;
400
49.2k
                        break;
401
402
7.38k
                    case 342:
403
                        // 342 is the entity handle of the BLOCK_RECORD table
404
                        // entry that corresponds to the arrowhead block.
405
7.38k
                        osArrowheadBlockHandle = szLineBuf;
406
7.38k
                        break;
407
408
2.66k
                    case 42:
409
                        // TODO figure out difference between 42 and 140 for
410
                        // arrowheadsize
411
2.66k
                        dfArrowheadSize = CPLAtof(szLineBuf);
412
2.66k
                        break;
413
414
34.0k
                    case 330:
415
34.0k
                        osBlockAttributeHandle = szLineBuf;
416
34.0k
                        break;
417
418
25.2k
                    case 302:
419
25.2k
                        if (osBlockAttributeHandle != "")
420
11.9k
                        {
421
11.9k
                            oBlockAttributes[osBlockAttributeHandle] =
422
11.9k
                                TextUnescape(szLineBuf, true);
423
11.9k
                            osBlockAttributeHandle = "";
424
11.9k
                        }
425
25.2k
                        break;
426
427
85.1k
                    case 91:
428
85.1k
                        osLeaderColor = szLineBuf;
429
85.1k
                        break;
430
431
3.87k
                    case 170:
432
3.87k
                        nLeaderLineType = atoi(szLineBuf);
433
3.87k
                        break;
434
435
1.27k
                    case 291:
436
1.27k
                        bHasDogleg = atoi(szLineBuf) != 0;
437
1.27k
                        break;
438
439
342k
                    default:
440
342k
                        TranslateGenericProperty(poOverallFeature, nCode,
441
342k
                                                 szLineBuf);
442
342k
                        break;
443
551k
                }
444
551k
                break;
445
446
551k
            case MLS_CONTEXT_DATA:
447
485k
                switch (nCode)
448
485k
                {
449
3.28k
                    case 301:
450
3.28k
                        nSection = MLS_COMMON;
451
3.28k
                        break;
452
453
38.7k
                    case 302:
454
38.7k
                        nSection = MLS_LEADER;
455
38.7k
                        break;
456
457
33.9k
                    case 304:
458
33.9k
                        osText = TextUnescape(szLineBuf, true);
459
33.9k
                        break;
460
461
11.4k
                    case 40:
462
11.4k
                        dfScale = CPLAtof(szLineBuf);
463
11.4k
                        break;
464
465
20.1k
                    case 340:
466
                        // 340 is the entity handle of the STYLE table entry
467
                        // that corresponds to the text style.
468
20.1k
                        osTextStyleHandle = szLineBuf;
469
20.1k
                        break;
470
471
10.9k
                    case 12:
472
10.9k
                        dfTextX = CPLAtof(szLineBuf);
473
10.9k
                        break;
474
475
11.6k
                    case 22:
476
11.6k
                        dfTextY = CPLAtof(szLineBuf);
477
11.6k
                        break;
478
479
3.89k
                    case 41:
480
3.89k
                        dfTextHeight = CPLAtof(szLineBuf);
481
3.89k
                        break;
482
483
2.74k
                    case 42:
484
2.74k
                        dfTextAngle = CPLAtof(szLineBuf) * 180 / M_PI;
485
2.74k
                        break;
486
487
1.24k
                    case 171:
488
1.24k
                        nTextAlignment = atoi(szLineBuf);
489
1.24k
                        break;
490
491
14.2k
                    case 341:
492
                        // 341 is the entity handle of the BLOCK_RECORD table
493
                        // entry that corresponds to the block content of this
494
                        // MLEADER.
495
14.2k
                        osBlockHandle = szLineBuf;
496
14.2k
                        break;
497
498
3.83k
                    case 15:
499
3.83k
                        oBlockTransformer.dfXOffset = CPLAtof(szLineBuf);
500
3.83k
                        break;
501
502
1.01k
                    case 25:
503
1.01k
                        oBlockTransformer.dfYOffset = CPLAtof(szLineBuf);
504
1.01k
                        break;
505
506
1.69k
                    case 16:
507
1.69k
                        oBlockTransformer.dfXScale = CPLAtof(szLineBuf);
508
1.69k
                        break;
509
510
1.22k
                    case 26:
511
1.22k
                        oBlockTransformer.dfYScale = CPLAtof(szLineBuf);
512
1.22k
                        break;
513
514
1.78k
                    case 46:
515
1.78k
                        oBlockTransformer.dfAngle = CPLAtof(szLineBuf);
516
1.78k
                        break;
517
485k
                }
518
485k
                break;
519
520
538k
            case MLS_LEADER:
521
538k
                switch (nCode)
522
538k
                {
523
24.4k
                    case 303:
524
24.4k
                        nSection = MLS_CONTEXT_DATA;
525
24.4k
                        aoLeaders.emplace_back(std::move(oLeader));
526
24.4k
                        oLeader = DXFMLEADERLeader();
527
24.4k
                        break;
528
529
48.6k
                    case 304:
530
48.6k
                        nSection = MLS_LEADER_LINE;
531
48.6k
                        break;
532
533
18.3k
                    case 10:
534
18.3k
                        oLeader.dfLandingX = CPLAtof(szLineBuf);
535
18.3k
                        break;
536
537
31.3k
                    case 20:
538
31.3k
                        oLeader.dfLandingY = CPLAtof(szLineBuf);
539
31.3k
                        break;
540
541
12.5k
                    case 11:
542
12.5k
                        oLeader.dfDoglegVectorX = CPLAtof(szLineBuf);
543
12.5k
                        break;
544
545
21.0k
                    case 21:
546
21.0k
                        oLeader.dfDoglegVectorY = CPLAtof(szLineBuf);
547
21.0k
                        break;
548
549
8.43k
                    case 12:
550
8.43k
                        dfCurrentX = CPLAtof(szLineBuf);
551
8.43k
                        break;
552
553
8.86k
                    case 22:
554
8.86k
                        dfCurrentY = CPLAtof(szLineBuf);
555
8.86k
                        break;
556
557
4.33k
                    case 13:
558
4.33k
                        dfCurrentX2 = CPLAtof(szLineBuf);
559
4.33k
                        break;
560
561
24.0k
                    case 23:
562
24.0k
                        dfCurrentY2 = CPLAtof(szLineBuf);
563
24.0k
                        oLeader.aoDoglegBreaks.push_back(std::make_pair(
564
24.0k
                            DXFTriple(dfCurrentX, dfCurrentY, 0.0),
565
24.0k
                            DXFTriple(dfCurrentX2, dfCurrentY2, 0.0)));
566
24.0k
                        break;
567
568
16.5k
                    case 40:
569
16.5k
                        oLeader.dfDoglegLength = CPLAtof(szLineBuf);
570
16.5k
                        break;
571
538k
                }
572
538k
                break;
573
574
1.60M
            case MLS_LEADER_LINE:
575
1.60M
                switch (nCode)
576
1.60M
                {
577
43.0k
                    case 305:
578
43.0k
                        nSection = MLS_LEADER;
579
43.0k
                        oLeader.aaoLeaderLines.emplace_back(
580
43.0k
                            std::move(oLeaderLine));
581
43.0k
                        oLeaderLine = std::vector<DXFMLEADERVertex>();
582
43.0k
                        break;
583
584
14.1k
                    case 10:
585
14.1k
                        dfCurrentX = CPLAtof(szLineBuf);
586
14.1k
                        break;
587
588
1.05M
                    case 20:
589
1.05M
                        dfCurrentY = CPLAtof(szLineBuf);
590
1.05M
                        oLeaderLine.push_back(
591
1.05M
                            DXFMLEADERVertex(dfCurrentX, dfCurrentY));
592
1.05M
                        break;
593
594
4.38k
                    case 90:
595
4.38k
                        nCurrentVertex = atoi(szLineBuf);
596
4.38k
                        if (nCurrentVertex >= oLeaderLine.size())
597
351
                        {
598
351
                            CPLError(CE_Warning, CPLE_AppDefined,
599
351
                                     "Wrong group code 90 in LEADER_LINE: %s",
600
351
                                     szLineBuf);
601
351
                            DXF_LAYER_READER_ERROR();
602
351
                            delete poOverallFeature;
603
351
                            return nullptr;
604
351
                        }
605
4.03k
                        break;
606
607
6.83k
                    case 11:
608
6.83k
                        dfCurrentX = CPLAtof(szLineBuf);
609
6.83k
                        break;
610
611
51.8k
                    case 21:
612
51.8k
                        dfCurrentY = CPLAtof(szLineBuf);
613
51.8k
                        break;
614
615
2.99k
                    case 12:
616
2.99k
                        dfCurrentX2 = CPLAtof(szLineBuf);
617
2.99k
                        break;
618
619
49.3k
                    case 22:
620
49.3k
                        if (nCurrentVertex >= oLeaderLine.size())
621
710
                        {
622
710
                            CPLError(CE_Warning, CPLE_AppDefined,
623
710
                                     "Misplaced group code 22 in LEADER_LINE");
624
710
                            DXF_LAYER_READER_ERROR();
625
710
                            delete poOverallFeature;
626
710
                            return nullptr;
627
710
                        }
628
48.6k
                        dfCurrentY2 = CPLAtof(szLineBuf);
629
48.6k
                        oLeaderLine[nCurrentVertex].aoBreaks.push_back(
630
48.6k
                            std::make_pair(
631
48.6k
                                DXFTriple(dfCurrentX, dfCurrentY, 0.0),
632
48.6k
                                DXFTriple(dfCurrentX2, dfCurrentY2, 0.0)));
633
48.6k
                        break;
634
1.60M
                }
635
1.60M
                break;
636
3.18M
        }
637
3.18M
    }
638
639
98.4k
    if (nCode < 0)
640
5.05k
    {
641
5.05k
        DXF_LAYER_READER_ERROR();
642
5.05k
        delete poOverallFeature;
643
5.05k
        return nullptr;
644
5.05k
    }
645
93.4k
    if (nCode == 0)
646
93.4k
        poDS->UnreadValue();
647
648
    // Convert the block handle to a block name. If there is no block,
649
    // osBlockName will remain empty.
650
93.4k
    CPLString osBlockName;
651
652
93.4k
    if (osBlockHandle != "")
653
5.68k
        osBlockName = poDS->GetBlockNameByRecordHandle(osBlockHandle);
654
655
    /* -------------------------------------------------------------------- */
656
    /*      Add the landing and arrowhead onto each leader line, and add    */
657
    /*      the dogleg, if present, onto the leader.                        */
658
    /* -------------------------------------------------------------------- */
659
93.4k
    OGRDXFFeature *poLeaderFeature = poOverallFeature->CloneDXFFeature();
660
93.4k
    poLeaderFeature->oStyleProperties["Color"] = osLeaderColor;
661
662
93.4k
    OGRMultiLineString *poMLS = new OGRMultiLineString();
663
664
    // Arrowheads should be the same color as the leader line. If the leader
665
    // line is ByBlock or ByLayer then the arrowhead should be "owned" by the
666
    // overall feature for styling purposes.
667
93.4k
    OGRDXFFeature *poArrowheadOwningFeature = poLeaderFeature;
668
93.4k
    if ((atoi(osLeaderColor) & 0xC2000000) == 0xC0000000)
669
412
        poArrowheadOwningFeature = poOverallFeature;
670
671
93.4k
    for (std::vector<DXFMLEADERLeader>::iterator oIt = aoLeaders.begin();
672
116k
         nLeaderLineType != MLT_NONE && oIt != aoLeaders.end(); ++oIt)
673
23.2k
    {
674
23.2k
        const bool bLeaderHasDogleg =
675
23.2k
            bHasDogleg && nLeaderLineType != MLT_SPLINE &&
676
23.2k
            oIt->dfDoglegLength != 0.0 &&
677
23.2k
            (oIt->dfDoglegVectorX != 0.0 || oIt->dfDoglegVectorY != 0.0);
678
679
        // We assume that the dogleg vector in the DXF is a unit vector.
680
        // Safe assumption? Who knows. The documentation is so bad.
681
23.2k
        const double dfDoglegX =
682
23.2k
            oIt->dfLandingX + oIt->dfDoglegVectorX * oIt->dfDoglegLength;
683
23.2k
        const double dfDoglegY =
684
23.2k
            oIt->dfLandingY + oIt->dfDoglegVectorY * oIt->dfDoglegLength;
685
686
        // When the dogleg is turned off or we are in spline mode, it seems
687
        // that the dogleg and landing data are still present in the DXF file,
688
        // but they are not supposed to be drawn.
689
23.2k
        if (!bHasDogleg || nLeaderLineType == MLT_SPLINE)
690
1.08k
        {
691
1.08k
            oIt->dfLandingX = dfDoglegX;
692
1.08k
            oIt->dfLandingY = dfDoglegY;
693
1.08k
        }
694
695
        // Iterate through each leader line
696
23.2k
        for (const auto &aoLineVertices : oIt->aaoLeaderLines)
697
39.2k
        {
698
39.2k
            if (aoLineVertices.empty())
699
19.7k
                continue;
700
701
19.4k
            OGRLineString *poLeaderLine = new OGRLineString();
702
703
            // Get the first line segment for arrowhead purposes
704
19.4k
            poLeaderLine->addPoint(aoLineVertices[0].oCoords.dfX,
705
19.4k
                                   aoLineVertices[0].oCoords.dfY);
706
707
19.4k
            if (aoLineVertices.size() > 1)
708
6.46k
            {
709
6.46k
                poLeaderLine->addPoint(aoLineVertices[1].oCoords.dfX,
710
6.46k
                                       aoLineVertices[1].oCoords.dfY);
711
6.46k
            }
712
12.9k
            else
713
12.9k
            {
714
12.9k
                poLeaderLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
715
12.9k
            }
716
717
            // Add an arrowhead if required
718
19.4k
            InsertArrowhead(poArrowheadOwningFeature, osArrowheadBlockHandle,
719
19.4k
                            poLeaderLine, dfArrowheadSize * dfScale);
720
721
19.4k
            poLeaderLine->setNumPoints(1);
722
723
            // Go through the vertices of the leader line, adding them,
724
            // as well as break start and end points, to the linestring.
725
838k
            for (size_t iVertex = 0; iVertex < aoLineVertices.size(); iVertex++)
726
818k
            {
727
818k
                if (iVertex > 0)
728
799k
                {
729
799k
                    poLeaderLine->addPoint(aoLineVertices[iVertex].oCoords.dfX,
730
799k
                                           aoLineVertices[iVertex].oCoords.dfY);
731
799k
                }
732
733
                // Breaks are ignored for spline leaders
734
818k
                if (nLeaderLineType != MLT_SPLINE)
735
15.8k
                {
736
15.8k
                    for (const auto &oBreak : aoLineVertices[iVertex].aoBreaks)
737
10.5k
                    {
738
10.5k
                        poLeaderLine->addPoint(oBreak.first.dfX,
739
10.5k
                                               oBreak.first.dfY);
740
741
10.5k
                        poMLS->addGeometryDirectly(poLeaderLine);
742
10.5k
                        poLeaderLine = new OGRLineString();
743
744
10.5k
                        poLeaderLine->addPoint(oBreak.second.dfX,
745
10.5k
                                               oBreak.second.dfY);
746
10.5k
                    }
747
15.8k
                }
748
818k
            }
749
750
            // Add the final vertex (the landing) to the end of the line
751
19.4k
            poLeaderLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
752
753
            // Make the spline geometry for spline leaders
754
19.4k
            if (nLeaderLineType == MLT_SPLINE)
755
8.15k
            {
756
8.15k
                DXFTriple oEndTangent;
757
8.15k
                if (osBlockName.empty())
758
7.78k
                {
759
7.78k
                    oEndTangent = DXFTriple(oIt->dfDoglegVectorX,
760
7.78k
                                            oIt->dfDoglegVectorY, 0);
761
7.78k
                }
762
8.15k
                InterpolateSpline(poLeaderLine, oEndTangent);
763
8.15k
            }
764
765
19.4k
            poMLS->addGeometryDirectly(poLeaderLine);
766
19.4k
        }
767
768
        // Add the dogleg as a separate line in the MLS
769
23.2k
        if (bLeaderHasDogleg)
770
3.71k
        {
771
3.71k
            OGRLineString *poDoglegLine = new OGRLineString();
772
3.71k
            poDoglegLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
773
774
            // Interrupt the dogleg line at breaks
775
3.71k
            for (const auto &oBreak : oIt->aoDoglegBreaks)
776
3.78k
            {
777
3.78k
                poDoglegLine->addPoint(oBreak.first.dfX, oBreak.first.dfY);
778
779
3.78k
                poMLS->addGeometryDirectly(poDoglegLine);
780
3.78k
                poDoglegLine = new OGRLineString();
781
782
3.78k
                poDoglegLine->addPoint(oBreak.second.dfX, oBreak.second.dfY);
783
3.78k
            }
784
785
3.71k
            poDoglegLine->addPoint(dfDoglegX, dfDoglegY);
786
3.71k
            poMLS->addGeometryDirectly(poDoglegLine);
787
3.71k
        }
788
23.2k
    }
789
790
93.4k
    poLeaderFeature->SetGeometryDirectly(poMLS);
791
792
93.4k
    PrepareLineStyle(poLeaderFeature, poOverallFeature);
793
794
    /* -------------------------------------------------------------------- */
795
    /*      If we have block content, insert that block.                    */
796
    /* -------------------------------------------------------------------- */
797
798
93.4k
    if (osBlockName != "")
799
3.06k
    {
800
3.06k
        oBlockTransformer.dfXScale *= dfScale;
801
3.06k
        oBlockTransformer.dfYScale *= dfScale;
802
803
3.06k
        DXFBlockDefinition *poBlock = poDS->LookupBlock(osBlockName);
804
805
3.06k
        std::map<OGRDXFFeature *, CPLString> oBlockAttributeValues;
806
807
        // If we have block attributes and will need to output them,
808
        // go through all the features on this block, looking for
809
        // ATTDEFs whose handle is in our list of attribute handles
810
3.06k
        if (poBlock && !oBlockAttributes.empty() &&
811
3.06k
            (poDS->InlineBlocks() ||
812
1.58k
             poOverallFeature->GetFieldIndex("BlockAttributes") != -1))
813
1.17k
        {
814
1.17k
            for (std::vector<OGRDXFFeature *>::iterator oIt =
815
1.17k
                     poBlock->apoFeatures.begin();
816
82.8k
                 oIt != poBlock->apoFeatures.end(); ++oIt)
817
81.6k
            {
818
81.6k
                const char *pszHandle =
819
81.6k
                    (*oIt)->GetFieldAsString("EntityHandle");
820
821
81.6k
                if (pszHandle && oBlockAttributes.count(pszHandle) > 0)
822
60.3k
                    oBlockAttributeValues[*oIt] = oBlockAttributes[pszHandle];
823
81.6k
            }
824
1.17k
        }
825
826
3.06k
        OGRDXFFeature *poBlockFeature = poOverallFeature->CloneDXFFeature();
827
828
        // If not inlining the block, insert a reference and add attributes
829
        // to this feature.
830
3.06k
        if (!poDS->InlineBlocks())
831
1.47k
        {
832
1.47k
            poBlockFeature = InsertBlockReference(
833
1.47k
                osBlockName, oBlockTransformer, poBlockFeature);
834
835
1.47k
            if (!oBlockAttributes.empty() &&
836
1.47k
                poOverallFeature->GetFieldIndex("BlockAttributes") != -1)
837
0
            {
838
0
                std::vector<char *> apszAttribs;
839
840
0
                for (std::map<OGRDXFFeature *, CPLString>::iterator oIt =
841
0
                         oBlockAttributeValues.begin();
842
0
                     oIt != oBlockAttributeValues.end(); ++oIt)
843
0
                {
844
                    // Store the attribute tag and the text value as
845
                    // a space-separated entry in the BlockAttributes field
846
0
                    CPLString osAttribString = oIt->first->osAttributeTag;
847
0
                    osAttribString += " ";
848
0
                    osAttribString += oIt->second;
849
850
0
                    apszAttribs.push_back(
851
0
                        new char[osAttribString.length() + 1]);
852
0
                    CPLStrlcpy(apszAttribs.back(), osAttribString.c_str(),
853
0
                               osAttribString.length() + 1);
854
0
                }
855
856
0
                apszAttribs.push_back(nullptr);
857
858
0
                poBlockFeature->SetField("BlockAttributes", &apszAttribs[0]);
859
0
            }
860
861
1.47k
            apoPendingFeatures.push(poBlockFeature);
862
1.47k
        }
863
1.59k
        else
864
1.59k
        {
865
            // Insert the block inline.
866
1.59k
            OGRDXFFeatureQueue apoExtraFeatures;
867
1.59k
            try
868
1.59k
            {
869
1.59k
                poBlockFeature = InsertBlockInline(
870
1.59k
                    CPLGetErrorCounter(), osBlockName, oBlockTransformer,
871
1.59k
                    poBlockFeature, apoExtraFeatures, true,
872
1.59k
                    poDS->ShouldMergeBlockGeometries());
873
1.59k
            }
874
1.59k
            catch (const std::invalid_argument &)
875
1.59k
            {
876
                // Block doesn't exist
877
210
                delete poBlockFeature;
878
210
                poBlockFeature = nullptr;
879
210
            }
880
881
            // Add the block geometries to the pending feature stack.
882
1.59k
            if (poBlockFeature)
883
652
            {
884
652
                apoPendingFeatures.push(poBlockFeature);
885
652
            }
886
75.9k
            while (!apoExtraFeatures.empty())
887
74.3k
            {
888
74.3k
                apoPendingFeatures.push(apoExtraFeatures.front());
889
74.3k
                apoExtraFeatures.pop();
890
74.3k
            }
891
892
            // Also add any attributes to the pending feature stack.
893
1.59k
            for (std::map<OGRDXFFeature *, CPLString>::iterator oIt =
894
1.59k
                     oBlockAttributeValues.begin();
895
61.8k
                 oIt != oBlockAttributeValues.end(); ++oIt)
896
60.3k
            {
897
60.3k
                OGRDXFFeature *poAttribFeature = oIt->first->CloneDXFFeature();
898
899
60.3k
                poAttribFeature->SetField("Text", oIt->second);
900
901
                // Replace text in the style string
902
60.3k
                const char *poStyleString = poAttribFeature->GetStyleString();
903
60.3k
                if (poStyleString && STARTS_WITH(poStyleString, "LABEL("))
904
3.00k
                {
905
3.00k
                    CPLString osNewStyle = poStyleString;
906
3.00k
                    const size_t nTextStartPos = osNewStyle.find(",t:\"");
907
3.00k
                    if (nTextStartPos != std::string::npos)
908
3.00k
                    {
909
3.00k
                        size_t nTextEndPos = nTextStartPos + 4;
910
219k
                        while (nTextEndPos < osNewStyle.size() &&
911
219k
                               osNewStyle[nTextEndPos] != '\"')
912
216k
                        {
913
216k
                            nTextEndPos++;
914
216k
                            if (osNewStyle[nTextEndPos] == '\\')
915
4.92k
                                nTextEndPos++;
916
216k
                        }
917
918
3.00k
                        if (nTextEndPos < osNewStyle.size())
919
2.14k
                        {
920
2.14k
                            osNewStyle.replace(
921
2.14k
                                nTextStartPos + 4,
922
2.14k
                                nTextEndPos - (nTextStartPos + 4), oIt->second);
923
2.14k
                            poAttribFeature->SetStyleString(osNewStyle);
924
2.14k
                        }
925
3.00k
                    }
926
3.00k
                }
927
928
                // The following bits are copied from
929
                // OGRDXFLayer::InsertBlockInline
930
60.3k
                if (poAttribFeature->GetGeometryRef())
931
60.0k
                {
932
60.0k
                    poAttribFeature->GetGeometryRef()->transform(
933
60.0k
                        &oBlockTransformer);
934
60.0k
                }
935
936
60.3k
                if (EQUAL(poAttribFeature->GetFieldAsString("Layer"), "0") &&
937
60.3k
                    !EQUAL(poOverallFeature->GetFieldAsString("Layer"), ""))
938
3
                {
939
3
                    poAttribFeature->SetField(
940
3
                        "Layer", poOverallFeature->GetFieldAsString("Layer"));
941
3
                }
942
943
60.3k
                PrepareFeatureStyle(poAttribFeature, poOverallFeature);
944
945
60.3k
                ACAdjustText(oBlockTransformer.dfAngle * 180 / M_PI,
946
60.3k
                             oBlockTransformer.dfXScale,
947
60.3k
                             oBlockTransformer.dfYScale, poAttribFeature);
948
949
60.3k
                if (!EQUAL(poOverallFeature->GetFieldAsString("EntityHandle"),
950
60.3k
                           ""))
951
264
                {
952
264
                    poAttribFeature->SetField(
953
264
                        "EntityHandle",
954
264
                        poOverallFeature->GetFieldAsString("EntityHandle"));
955
264
                }
956
957
60.3k
                apoPendingFeatures.push(poAttribFeature);
958
60.3k
            }
959
1.59k
        }
960
3.06k
    }
961
962
    /* -------------------------------------------------------------------- */
963
    /*      Prepare a new feature to serve as the leader text label         */
964
    /*      refeature.  We will push it onto the layer as a pending           */
965
    /*      feature for the next feature read.                              */
966
    /* -------------------------------------------------------------------- */
967
968
93.4k
    if (osText.empty() || osText == " ")
969
82.2k
    {
970
82.2k
        delete poOverallFeature;
971
82.2k
        return poLeaderFeature;
972
82.2k
    }
973
974
11.2k
    OGRDXFFeature *poLabelFeature = poOverallFeature->CloneDXFFeature();
975
976
11.2k
    poLabelFeature->SetField("Text", osText);
977
11.2k
    poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
978
979
11.2k
    CPLString osStyle;
980
11.2k
    char szBuffer[64];
981
982
11.2k
    const CPLString osStyleName =
983
11.2k
        poDS->GetTextStyleNameByHandle(osTextStyleHandle);
984
985
    // Font name
986
11.2k
    osStyle.Printf("LABEL(f:\"");
987
988
    // Preserve legacy behavior of specifying "Arial" as a default font name.
989
11.2k
    osStyle += poDS->LookupTextStyleProperty(osStyleName, "Font", "Arial");
990
991
11.2k
    osStyle += "\"";
992
993
    // Bold, italic
994
11.2k
    if (EQUAL(poDS->LookupTextStyleProperty(osStyleName, "Bold", "0"), "1"))
995
291
    {
996
291
        osStyle += ",bo:1";
997
291
    }
998
11.2k
    if (EQUAL(poDS->LookupTextStyleProperty(osStyleName, "Italic", "0"), "1"))
999
291
    {
1000
291
        osStyle += ",it:1";
1001
291
    }
1002
1003
11.2k
    osStyle +=
1004
11.2k
        CPLString().Printf(",t:\"%s\",p:%d", osText.c_str(),
1005
11.2k
                           nTextAlignment + 6);  // 7,8,9: vertical align top
1006
1007
11.2k
    if (dfTextAngle != 0.0)
1008
690
    {
1009
690
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
1010
690
        osStyle += CPLString().Printf(",a:%s", szBuffer);
1011
690
    }
1012
1013
11.2k
    if (dfTextHeight != 0.0)
1014
10.7k
    {
1015
10.7k
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight);
1016
10.7k
        osStyle += CPLString().Printf(",s:%sg", szBuffer);
1017
10.7k
    }
1018
1019
11.2k
    const char *pszWidthFactor =
1020
11.2k
        poDS->LookupTextStyleProperty(osStyleName, "Width", "1");
1021
11.2k
    if (pszWidthFactor && CPLAtof(pszWidthFactor) != 1.0)
1022
73
    {
1023
73
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.4g",
1024
73
                    CPLAtof(pszWidthFactor) * 100.0);
1025
73
        osStyle += CPLString().Printf(",w:%s", szBuffer);
1026
73
    }
1027
1028
    // Color
1029
11.2k
    osStyle += ",c:";
1030
11.2k
    osStyle += poLabelFeature->GetColor(poDS);
1031
1032
11.2k
    osStyle += ")";
1033
1034
11.2k
    poLabelFeature->SetStyleString(osStyle);
1035
1036
11.2k
    apoPendingFeatures.push(poLabelFeature);
1037
1038
11.2k
    delete poOverallFeature;
1039
11.2k
    return poLeaderFeature;
1040
93.4k
}
1041
1042
/************************************************************************/
1043
/*                     GenerateDefaultArrowhead()                       */
1044
/*                                                                      */
1045
/*      Generates the default DWG/DXF arrowhead (a filled triangle      */
1046
/*      with a 3:1 aspect ratio) on the end of the line segment         */
1047
/*      defined by the two points.                                      */
1048
/************************************************************************/
1049
static void GenerateDefaultArrowhead(OGRDXFFeature *const poArrowheadFeature,
1050
                                     const OGRPoint &oPoint1,
1051
                                     const OGRPoint &oPoint2,
1052
                                     const double dfArrowheadScale)
1053
1054
71.9k
{
1055
    // calculate the baseline to be expanded out into arrowheads
1056
71.9k
    const double dfParallelPartX =
1057
71.9k
        dfArrowheadScale * (oPoint2.getX() - oPoint1.getX());
1058
71.9k
    const double dfParallelPartY =
1059
71.9k
        dfArrowheadScale * (oPoint2.getY() - oPoint1.getY());
1060
    // ...and drop a perpendicular
1061
71.9k
    const double dfPerpPartX = dfParallelPartY;
1062
71.9k
    const double dfPerpPartY = -dfParallelPartX;
1063
1064
71.9k
    OGRLinearRing *poLinearRing = new OGRLinearRing();
1065
71.9k
    poLinearRing->setPoint(
1066
71.9k
        0, oPoint1.getX() + dfParallelPartX + dfPerpPartX / 6,
1067
71.9k
        oPoint1.getY() + dfParallelPartY + dfPerpPartY / 6, oPoint1.getZ());
1068
71.9k
    poLinearRing->setPoint(1, oPoint1.getX(), oPoint1.getY(), oPoint1.getZ());
1069
71.9k
    poLinearRing->setPoint(
1070
71.9k
        2, oPoint1.getX() + dfParallelPartX - dfPerpPartX / 6,
1071
71.9k
        oPoint1.getY() + dfParallelPartY - dfPerpPartY / 6, oPoint1.getZ());
1072
71.9k
    poLinearRing->closeRings();
1073
1074
71.9k
    OGRPolygon *poPoly = new OGRPolygon();
1075
71.9k
    poPoly->addRingDirectly(poLinearRing);
1076
1077
71.9k
    poArrowheadFeature->SetGeometryDirectly(poPoly);
1078
71.9k
}
1079
1080
/************************************************************************/
1081
/*                          InsertArrowhead()                           */
1082
/*                                                                      */
1083
/*      Inserts the specified arrowhead block at the start of the       */
1084
/*      first segment of the given line string (or the end of the       */
1085
/*      last segment if bReverse is false).  2D only.                   */
1086
/*                                                                      */
1087
/*      The first (last) point of the line string may be updated.       */
1088
/************************************************************************/
1089
void OGRDXFLayer::InsertArrowhead(OGRDXFFeature *const poFeature,
1090
                                  const CPLString &osBlockHandle,
1091
                                  OGRLineString *const poLine,
1092
                                  const double dfArrowheadSize,
1093
                                  const bool bReverse /* = false */)
1094
238k
{
1095
238k
    OGRPoint oPoint1, oPoint2;
1096
238k
    poLine->getPoint(bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1);
1097
238k
    poLine->getPoint(bReverse ? poLine->getNumPoints() - 2 : 1, &oPoint2);
1098
1099
238k
    const double dfFirstSegmentLength = PointDist(
1100
238k
        oPoint1.getX(), oPoint1.getY(), oPoint2.getX(), oPoint2.getY());
1101
1102
    // AutoCAD only displays an arrowhead if the length of the arrowhead
1103
    // is less than or equal to half the length of the line segment
1104
238k
    if (dfArrowheadSize == 0.0 || dfFirstSegmentLength == 0.0 ||
1105
238k
        dfArrowheadSize > 0.5 * dfFirstSegmentLength)
1106
165k
    {
1107
165k
        return;
1108
165k
    }
1109
1110
72.7k
    OGRDXFFeature *poArrowheadFeature = poFeature->CloneDXFFeature();
1111
1112
    // Convert the block handle to a block name.
1113
72.7k
    CPLString osBlockName = "";
1114
1115
72.7k
    if (osBlockHandle != "")
1116
5.83k
        osBlockName = poDS->GetBlockNameByRecordHandle(osBlockHandle);
1117
1118
72.7k
    OGRDXFFeatureQueue apoExtraFeatures;
1119
1120
    // If the block doesn't exist, we need to fall back to the
1121
    // default arrowhead.
1122
72.7k
    if (osBlockName == "")
1123
71.9k
    {
1124
71.9k
        GenerateDefaultArrowhead(poArrowheadFeature, oPoint1, oPoint2,
1125
71.9k
                                 dfArrowheadSize / dfFirstSegmentLength);
1126
1127
71.9k
        PrepareBrushStyle(poArrowheadFeature);
1128
71.9k
    }
1129
796
    else
1130
796
    {
1131
        // Build a transformer to insert the arrowhead block with the
1132
        // required location, angle and scale.
1133
796
        OGRDXFInsertTransformer oTransformer;
1134
796
        oTransformer.dfXOffset = oPoint1.getX();
1135
796
        oTransformer.dfYOffset = oPoint1.getY();
1136
796
        oTransformer.dfZOffset = oPoint1.getZ();
1137
        // Arrowhead blocks always point to the right (--->)
1138
796
        oTransformer.dfAngle = atan2(oPoint2.getY() - oPoint1.getY(),
1139
796
                                     oPoint2.getX() - oPoint1.getX()) +
1140
796
                               M_PI;
1141
796
        oTransformer.dfXScale = oTransformer.dfYScale = oTransformer.dfZScale =
1142
796
            dfArrowheadSize;
1143
1144
        // Insert the block.
1145
796
        try
1146
796
        {
1147
796
            poArrowheadFeature = InsertBlockInline(
1148
796
                CPLGetErrorCounter(), osBlockName, std::move(oTransformer),
1149
796
                poArrowheadFeature, apoExtraFeatures, true, false);
1150
796
        }
1151
796
        catch (const std::invalid_argument &)
1152
796
        {
1153
            // Supposedly the block doesn't exist. But what has probably
1154
            // happened is that the block exists in the DXF, but it contains
1155
            // no entities, so the data source didn't read it in.
1156
            // In this case, no arrowhead is required.
1157
291
            delete poArrowheadFeature;
1158
291
            poArrowheadFeature = nullptr;
1159
291
        }
1160
796
    }
1161
1162
    // Add the arrowhead geometries to the pending feature stack.
1163
72.7k
    if (poArrowheadFeature)
1164
71.9k
    {
1165
71.9k
        apoPendingFeatures.push(poArrowheadFeature);
1166
71.9k
    }
1167
113k
    while (!apoExtraFeatures.empty())
1168
40.9k
    {
1169
40.9k
        apoPendingFeatures.push(apoExtraFeatures.front());
1170
40.9k
        apoExtraFeatures.pop();
1171
40.9k
    }
1172
1173
    // Move the endpoint of the line out of the way of the arrowhead.
1174
    // We assume that arrowheads are 1 unit long, except for a list
1175
    // of specific block names which are treated as having no length
1176
1177
72.7k
    static const char *apszSpecialArrowheads[] = {
1178
72.7k
        "_ArchTick", "_DotSmall", "_Integral", "_None", "_Oblique", "_Small"};
1179
1180
72.7k
    if (std::find(apszSpecialArrowheads, apszSpecialArrowheads + 6,
1181
72.7k
                  osBlockName) == (apszSpecialArrowheads + 6))
1182
72.3k
    {
1183
72.3k
        oPoint1.setX(oPoint1.getX() + dfArrowheadSize *
1184
72.3k
                                          (oPoint2.getX() - oPoint1.getX()) /
1185
72.3k
                                          dfFirstSegmentLength);
1186
72.3k
        oPoint1.setY(oPoint1.getY() + dfArrowheadSize *
1187
72.3k
                                          (oPoint2.getY() - oPoint1.getY()) /
1188
72.3k
                                          dfFirstSegmentLength);
1189
1190
72.3k
        poLine->setPoint(bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1);
1191
72.3k
    }
1192
72.7k
}
1193
1194
/************************************************************************/
1195
/*                        basis(), rbspline2()                          */
1196
/*                                                                      */
1197
/*      Spline calculation functions defined in intronurbs.cpp.         */
1198
/************************************************************************/
1199
void basis(int c, double t, int npts, double x[], double N[]);
1200
void rbspline2(int npts, int k, int p1, double b[], double h[],
1201
               bool bCalculateKnots, double x[], double p[]);
1202
1203
#if defined(__GNUC__) && __GNUC__ >= 6
1204
#pragma GCC diagnostic push
1205
#pragma GCC diagnostic ignored "-Wnull-dereference"
1206
#endif
1207
1208
namespace
1209
{
1210
inline void setRow(GDALMatrix &m, int row, DXFTriple const &t)
1211
155k
{
1212
155k
    m(row, 0) = t.dfX;
1213
155k
    m(row, 1) = t.dfY;
1214
155k
    m(row, 2) = t.dfZ;
1215
155k
}
1216
}  // namespace
1217
1218
/************************************************************************/
1219
/*                      GetBSplineControlPoints()                       */
1220
/*                                                                      */
1221
/*      Evaluates the control points for the B-spline of given degree   */
1222
/*      that interpolates the given data points, using the given        */
1223
/*      parameters, start tangent and end tangent.  The parameters      */
1224
/*      and knot vector must be increasing sequences with first         */
1225
/*      element 0 and last element 1.  Given n data points, there       */
1226
/*      must be n parameters and n + nDegree + 3 knots.                 */
1227
/*                                                                      */
1228
/*      It is recommended to match AutoCAD by generating a knot         */
1229
/*      vector from the parameters as follows:                          */
1230
/*              0 0 ... 0 adfParameters 1 1 ... 1                       */
1231
/*        (nDegree zeros)               (nDegree ones)                  */
1232
/*      To fully match AutoCAD's behavior, a chord-length              */
1233
/*      parameterisation should be used, and the start and end          */
1234
/*      tangent vectors should be multiplied by the total chord         */
1235
/*      length of all chords.                                           */
1236
/*                                                                      */
1237
/*      Reference: Piegl, L., Tiller, W. (1995), The NURBS Book,        */
1238
/*      2nd ed. (Springer), sections 2.2 and 9.2.                       */
1239
/*      Although this book contains implementations of algorithms,      */
1240
/*      this function is an original implementation based on the        */
1241
/*      concepts discussed in the book and was written without          */
1242
/*      reference to Piegl and Tiller's implementations.                */
1243
/************************************************************************/
1244
static std::vector<DXFTriple>
1245
GetBSplineControlPoints(const std::vector<double> &adfParameters,
1246
                        const std::vector<double> &adfKnots,
1247
                        const std::vector<DXFTriple> &aoDataPoints,
1248
                        const int nDegree, DXFTriple oStartTangent,
1249
                        DXFTriple oEndTangent)
1250
28.7k
{
1251
28.7k
    CPLAssert(nDegree > 1);
1252
1253
    // Count the number of data points
1254
    // Note: The literature often sets n to one less than the number of data
1255
    // points for some reason, but we don't do that here
1256
28.7k
    const int nPoints = static_cast<int>(aoDataPoints.size());
1257
1258
28.7k
    CPLAssert(nPoints > 0);
1259
28.7k
    CPLAssert(nPoints == static_cast<int>(adfParameters.size()));
1260
1261
    // RAM consumption is quadratic in the number of control points.
1262
28.7k
    if (nPoints >
1263
28.7k
        atoi(CPLGetConfigOption("DXF_MAX_BSPLINE_CONTROL_POINTS", "2000")))
1264
138
    {
1265
138
        CPLError(CE_Failure, CPLE_AppDefined,
1266
138
                 "Too many control points (%d) for spline leader. "
1267
138
                 "Set DXF_MAX_BSPLINE_CONTROL_POINTS configuration "
1268
138
                 "option to a higher value to remove this limitation "
1269
138
                 "(at the cost of significant RAM consumption)",
1270
138
                 nPoints);
1271
138
        return std::vector<DXFTriple>();
1272
138
    }
1273
1274
    // We want to solve the linear system NP=D for P, where N is a coefficient
1275
    // matrix made up of values of the basis functions at each parameter
1276
    // value, with two additional rows for the endpoint tangent information.
1277
    // Each row relates to a different parameter.
1278
1279
    // Set up D as a matrix consisting initially of the data points
1280
28.6k
    GDALMatrix D(nPoints + 2, 3);
1281
1282
28.6k
    setRow(D, 0, aoDataPoints[0]);
1283
69.4k
    for (int iIndex = 1; iIndex < nPoints - 1; iIndex++)
1284
40.8k
        setRow(D, iIndex + 1, aoDataPoints[iIndex]);
1285
28.6k
    setRow(D, nPoints + 1, aoDataPoints[nPoints - 1]);
1286
1287
28.6k
    const double dfStartMultiplier = adfKnots[nDegree + 1] / nDegree;
1288
28.6k
    oStartTangent *= dfStartMultiplier;
1289
28.6k
    setRow(D, 1, oStartTangent);
1290
1291
28.6k
    const double dfEndMultiplier = (1.0 - adfKnots[nPoints + 1]) / nDegree;
1292
28.6k
    oEndTangent *= dfEndMultiplier;
1293
28.6k
    setRow(D, nPoints, oEndTangent);
1294
1295
28.6k
    GDALMatrix N(nPoints + 2, nPoints + 2);
1296
    // First control point will be the first data point
1297
28.6k
    N(0, 0) = 1.0;
1298
1299
    // Start tangent determines the second control point
1300
28.6k
    N(1, 0) = -1.0;
1301
28.6k
    N(1, 1) = 1.0;
1302
1303
    // Fill the middle rows of the matrix with basis function values. We
1304
    // have to use a temporary vector, because intronurbs' basis function
1305
    // requires an additional nDegree entries for temporary storage.
1306
28.6k
    std::vector<double> adfTempRow(nPoints + 2 + nDegree, 0.0);
1307
69.4k
    for (int iRow = 2; iRow < nPoints; iRow++)
1308
40.8k
    {
1309
40.8k
        basis(nDegree + 1, adfParameters[iRow - 1], nPoints + 2,
1310
40.8k
              const_cast<double *>(&adfKnots[0]) - 1, &adfTempRow[0] - 1);
1311
1.06M
        for (int iCol = 0; iCol < nPoints + 2; ++iCol)
1312
1.02M
            N(iRow, iCol) = adfTempRow[iCol];
1313
40.8k
    }
1314
1315
    // End tangent determines the second-last control point
1316
28.6k
    N(nPoints, nPoints) = -1.0;
1317
28.6k
    N(nPoints, nPoints + 1) = 1.0;
1318
1319
    // Last control point will be the last data point
1320
28.6k
    N(nPoints + 1, nPoints + 1) = 1.0;
1321
1322
    // Solve the linear system
1323
28.6k
    GDALMatrix P(nPoints + 2, 3);
1324
28.6k
    GDALLinearSystemSolve(N, D, P);
1325
1326
28.6k
    std::vector<DXFTriple> aoControlPoints(nPoints + 2);
1327
183k
    for (int iRow = 0; iRow < nPoints + 2; iRow++)
1328
155k
    {
1329
155k
        aoControlPoints[iRow].dfX = P(iRow, 0);
1330
155k
        aoControlPoints[iRow].dfY = P(iRow, 1);
1331
155k
        aoControlPoints[iRow].dfZ = P(iRow, 2);
1332
155k
    }
1333
1334
28.6k
    return aoControlPoints;
1335
28.7k
}
1336
1337
#if defined(__GNUC__) && __GNUC__ >= 6
1338
#pragma GCC diagnostic pop
1339
#endif
1340
1341
/************************************************************************/
1342
/*                         InterpolateSpline()                          */
1343
/*                                                                      */
1344
/*      Interpolates a cubic spline between the data points of the      */
1345
/*      given line string. The line string is updated with the new      */
1346
/*      spline geometry.                                                */
1347
/*                                                                      */
1348
/*      If an end tangent of (0,0,0) is given, the direction vector     */
1349
/*      of the last chord (line segment) is used.                       */
1350
/************************************************************************/
1351
static void InterpolateSpline(OGRLineString *const poLine,
1352
                              const DXFTriple &oEndTangentDirection)
1353
50.2k
{
1354
50.2k
    int nDataPoints = static_cast<int>(poLine->getNumPoints());
1355
50.2k
    if (nDataPoints < 2)
1356
17.4k
        return;
1357
1358
    // Transfer line vertices into DXFTriple objects
1359
32.8k
    std::vector<DXFTriple> aoDataPoints;
1360
32.8k
    OGRPoint oPrevPoint;
1361
944k
    for (int iIndex = 0; iIndex < nDataPoints; iIndex++)
1362
911k
    {
1363
911k
        OGRPoint oPoint;
1364
911k
        poLine->getPoint(iIndex, &oPoint);
1365
1366
        // Remove sequential duplicate points
1367
911k
        if (iIndex > 0 && oPrevPoint.Equals(&oPoint))
1368
357k
            continue;
1369
1370
554k
        aoDataPoints.push_back(
1371
554k
            DXFTriple(oPoint.getX(), oPoint.getY(), oPoint.getZ()));
1372
554k
        oPrevPoint = std::move(oPoint);
1373
554k
    }
1374
32.8k
    nDataPoints = static_cast<int>(aoDataPoints.size());
1375
32.8k
    if (nDataPoints < 2)
1376
3.17k
        return;
1377
1378
    // Work out the chord length parameterisation
1379
29.6k
    std::vector<double> adfParameters;
1380
29.6k
    adfParameters.push_back(0.0);
1381
548k
    for (int iIndex = 1; iIndex < nDataPoints; iIndex++)
1382
520k
    {
1383
520k
        const double dfParameter =
1384
520k
            adfParameters[iIndex - 1] +
1385
520k
            PointDist(aoDataPoints[iIndex - 1].dfX,
1386
520k
                      aoDataPoints[iIndex - 1].dfY,
1387
520k
                      aoDataPoints[iIndex - 1].dfZ, aoDataPoints[iIndex].dfX,
1388
520k
                      aoDataPoints[iIndex].dfY, aoDataPoints[iIndex].dfZ);
1389
1390
        // Bail out in pathological cases. This will happen when
1391
        // some lengths are very large (above 10^16) and others are
1392
        // very small (such as 1)
1393
520k
        if (dfParameter == adfParameters[iIndex - 1])
1394
879
            return;
1395
1396
519k
        adfParameters.push_back(dfParameter);
1397
519k
    }
1398
1399
28.7k
    const double dfTotalChordLength = adfParameters.back();
1400
1401
    // Start tangent can be worked out from the first chord
1402
28.7k
    DXFTriple oStartTangent(aoDataPoints[1].dfX - aoDataPoints[0].dfX,
1403
28.7k
                            aoDataPoints[1].dfY - aoDataPoints[0].dfY,
1404
28.7k
                            aoDataPoints[1].dfZ - aoDataPoints[0].dfZ);
1405
28.7k
    oStartTangent *= dfTotalChordLength / adfParameters[1];
1406
1407
    // If end tangent is zero, it is worked out from the last chord
1408
28.7k
    DXFTriple oEndTangent = oEndTangentDirection;
1409
28.7k
    if (oEndTangent.dfX == 0.0 && oEndTangent.dfY == 0.0 &&
1410
28.7k
        oEndTangent.dfZ == 0.0)
1411
23.5k
    {
1412
23.5k
        oEndTangent = DXFTriple(aoDataPoints[nDataPoints - 1].dfX -
1413
23.5k
                                    aoDataPoints[nDataPoints - 2].dfX,
1414
23.5k
                                aoDataPoints[nDataPoints - 1].dfY -
1415
23.5k
                                    aoDataPoints[nDataPoints - 2].dfY,
1416
23.5k
                                aoDataPoints[nDataPoints - 1].dfZ -
1417
23.5k
                                    aoDataPoints[nDataPoints - 2].dfZ);
1418
23.5k
        oEndTangent /= dfTotalChordLength - adfParameters[nDataPoints - 2];
1419
23.5k
    }
1420
1421
    // End tangent direction is multiplied by total chord length
1422
28.7k
    oEndTangent *= dfTotalChordLength;
1423
1424
    // Normalise the parameter vector
1425
546k
    for (int iIndex = 1; iIndex < nDataPoints; iIndex++)
1426
517k
        adfParameters[iIndex] /= dfTotalChordLength;
1427
1428
    // Generate a knot vector
1429
28.7k
    const int nDegree = 3;
1430
28.7k
    std::vector<double> adfKnots(aoDataPoints.size() + nDegree + 3, 0.0);
1431
28.7k
    std::copy(adfParameters.begin(), adfParameters.end(),
1432
28.7k
              adfKnots.begin() + nDegree);
1433
28.7k
    std::fill(adfKnots.end() - nDegree, adfKnots.end(), 1.0);
1434
1435
    // Calculate the spline control points
1436
28.7k
    std::vector<DXFTriple> aoControlPoints =
1437
28.7k
        GetBSplineControlPoints(adfParameters, adfKnots, aoDataPoints, nDegree,
1438
28.7k
                                oStartTangent, oEndTangent);
1439
28.7k
    const int nControlPoints = static_cast<int>(aoControlPoints.size());
1440
1441
28.7k
    if (nControlPoints == 0)
1442
138
        return;
1443
1444
    // Interpolate the spline using the intronurbs code
1445
28.6k
    int nWantedPoints = nControlPoints * 8;
1446
28.6k
    std::vector<double> adfWeights(nControlPoints, 1.0);
1447
28.6k
    std::vector<double> adfPoints(3 * nWantedPoints, 0.0);
1448
1449
28.6k
    rbspline2(nControlPoints, nDegree + 1, nWantedPoints,
1450
28.6k
              reinterpret_cast<double *>(&aoControlPoints[0]) - 1,
1451
28.6k
              &adfWeights[0] - 1, false, &adfKnots[0] - 1, &adfPoints[0] - 1);
1452
1453
    // Preserve 2D/3D status as we add the interpolated points to the line
1454
28.6k
    const int bIs3D = poLine->Is3D();
1455
28.6k
    poLine->empty();
1456
1.27M
    for (int iIndex = 0; iIndex < nWantedPoints; iIndex++)
1457
1.24M
    {
1458
1.24M
        poLine->addPoint(adfPoints[iIndex * 3], adfPoints[iIndex * 3 + 1],
1459
1.24M
                         adfPoints[iIndex * 3 + 2]);
1460
1.24M
    }
1461
28.6k
    if (!bIs3D)
1462
7.30k
        poLine->flattenTo2D();
1463
28.6k
}