Coverage Report

Created: 2025-08-11 09:23

/src/gdal/ogr/ogrsf_frmts/dxf/ogrdxf_dimension.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  DXF Translator
4
 * Purpose:  Implements translation support for DIMENSION elements as a part
5
 *           of the OGRDXFLayer class.
6
 * Author:   Frank Warmerdam, warmerdam@pobox.com
7
 *
8
 ******************************************************************************
9
 * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
10
 * Copyright (c) 2010, Even Rouault <even dot rouault at spatialys.com>
11
 * Copyright (c) 2017, Alan Thomas <alant@outlook.com.au>
12
 *
13
 * SPDX-License-Identifier: MIT
14
 ****************************************************************************/
15
16
#include "ogr_dxf.h"
17
#include "cpl_conv.h"
18
19
#include <stdexcept>
20
21
/************************************************************************/
22
/*                             PointDist()                              */
23
/************************************************************************/
24
25
#ifndef PointDist_defined
26
#define PointDist_defined
27
28
inline static double PointDist(double x1, double y1, double x2, double y2)
29
155k
{
30
155k
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
31
155k
}
32
#endif
33
34
/************************************************************************/
35
/*                         TranslateDIMENSION()                         */
36
/************************************************************************/
37
38
OGRDXFFeature *OGRDXFLayer::TranslateDIMENSION()
39
40
98.2k
{
41
98.2k
    char szLineBuf[257];
42
98.2k
    int nCode = 0;
43
    // int  nDimType = 0;
44
98.2k
    OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
45
98.2k
    double dfArrowX1 = 0.0;
46
98.2k
    double dfArrowY1 = 0.0;
47
    // double dfArrowZ1 = 0.0;
48
98.2k
    double dfTargetX1 = 0.0;
49
98.2k
    double dfTargetY1 = 0.0;
50
    // double dfTargetZ1 = 0.0;
51
98.2k
    double dfTargetX2 = 0.0;
52
98.2k
    double dfTargetY2 = 0.0;
53
    // double dfTargetZ2 = 0.0;
54
98.2k
    double dfTextX = 0.0;
55
98.2k
    double dfTextY = 0.0;
56
    // double dfTextZ = 0.0;
57
58
98.2k
    bool bReadyForDimstyleOverride = false;
59
60
98.2k
    bool bHaveBlock = false;
61
98.2k
    CPLString osBlockName;
62
98.2k
    CPLString osText;
63
64
98.2k
    std::map<CPLString, CPLString> oDimStyleProperties;
65
98.2k
    poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
66
67
694k
    while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
68
595k
    {
69
595k
        switch (nCode)
70
595k
        {
71
57.9k
            case 2:
72
57.9k
                bHaveBlock = true;
73
57.9k
                osBlockName = szLineBuf;
74
57.9k
                break;
75
76
36.3k
            case 3:
77
                // 3 is the dimension style name. We don't need to store it,
78
                // let's just fetch the dimension style properties
79
36.3k
                poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
80
36.3k
                break;
81
82
29.4k
            case 10:
83
29.4k
                dfArrowX1 = CPLAtof(szLineBuf);
84
29.4k
                break;
85
86
24.9k
            case 20:
87
24.9k
                dfArrowY1 = CPLAtof(szLineBuf);
88
24.9k
                break;
89
90
21.3k
            case 30:
91
                /* dfArrowZ1 = CPLAtof(szLineBuf); */
92
21.3k
                break;
93
94
6.02k
            case 11:
95
6.02k
                dfTextX = CPLAtof(szLineBuf);
96
6.02k
                break;
97
98
19.5k
            case 21:
99
19.5k
                dfTextY = CPLAtof(szLineBuf);
100
19.5k
                break;
101
102
8.40k
            case 31:
103
                /* dfTextZ = CPLAtof(szLineBuf); */
104
8.40k
                break;
105
106
12.7k
            case 13:
107
12.7k
                dfTargetX2 = CPLAtof(szLineBuf);
108
12.7k
                break;
109
110
16.9k
            case 23:
111
16.9k
                dfTargetY2 = CPLAtof(szLineBuf);
112
16.9k
                break;
113
114
2.88k
            case 33:
115
                /* dfTargetZ2 = CPLAtof(szLineBuf); */
116
2.88k
                break;
117
118
4.67k
            case 14:
119
4.67k
                dfTargetX1 = CPLAtof(szLineBuf);
120
4.67k
                break;
121
122
17.8k
            case 24:
123
17.8k
                dfTargetY1 = CPLAtof(szLineBuf);
124
17.8k
                break;
125
126
1.89k
            case 34:
127
                /* dfTargetZ1 = CPLAtof(szLineBuf); */
128
1.89k
                break;
129
130
1.68k
            case 70:
131
                /* nDimType = atoi(szLineBuf); */
132
1.68k
                break;
133
134
73.5k
            case 1:
135
73.5k
                osText = szLineBuf;
136
73.5k
                break;
137
138
9.53k
            case 1001:
139
9.53k
                bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
140
9.53k
                break;
141
142
14.3k
            case 1070:
143
14.3k
                if (bReadyForDimstyleOverride)
144
10.7k
                {
145
                    // Store DIMSTYLE override values in the dimension
146
                    // style property map. The nInnerCode values match the
147
                    // group codes used in the DIMSTYLE table.
148
10.7k
                    const int nInnerCode = atoi(szLineBuf);
149
10.7k
                    const char *pszProperty =
150
10.7k
                        ACGetDimStylePropertyName(nInnerCode);
151
10.7k
                    if (pszProperty)
152
8.19k
                    {
153
8.19k
                        nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
154
8.19k
                        if (nCode == 1005 || nCode == 1040 || nCode == 1070)
155
7.17k
                            oDimStyleProperties[pszProperty] = szLineBuf;
156
8.19k
                    }
157
10.7k
                }
158
14.3k
                break;
159
160
235k
            default:
161
235k
                TranslateGenericProperty(poFeature, nCode, szLineBuf);
162
235k
                break;
163
595k
        }
164
595k
    }
165
98.2k
    if (nCode < 0)
166
1.85k
    {
167
1.85k
        DXF_LAYER_READER_ERROR();
168
1.85k
        delete poFeature;
169
1.85k
        return nullptr;
170
1.85k
    }
171
96.4k
    if (nCode == 0)
172
96.4k
        poDS->UnreadValue();
173
174
    // If osBlockName (group code 2) refers to a valid block, we can just insert
175
    // that block - that should give us the correctly exploded geometry of this
176
    // dimension. If this value is missing, or doesn't refer to a valid block,
177
    // we will need to use our own logic to generate the dimension lines.
178
96.4k
    if (bHaveBlock && osBlockName.length() > 0)
179
31.3k
    {
180
        // Always inline the block, because this is an anonymous block that the
181
        // user likely doesn't know or care about
182
31.3k
        try
183
31.3k
        {
184
31.3k
            OGRDXFFeature *poBlockFeature = InsertBlockInline(
185
31.3k
                CPLGetErrorCounter(), osBlockName, OGRDXFInsertTransformer(),
186
31.3k
                poFeature, apoPendingFeatures, true, false);
187
188
31.3k
            return poBlockFeature;  // may be NULL but that is OK
189
31.3k
        }
190
31.3k
        catch (const std::invalid_argument &)
191
31.3k
        {
192
30.7k
        }
193
31.3k
    }
194
195
    // Unpack the dimension style
196
95.8k
    const double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
197
95.8k
    const double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
198
95.8k
    const double dfExtLineExtendLength = CPLAtof(oDimStyleProperties["DIMEXE"]);
199
95.8k
    const double dfExtLineOffset = CPLAtof(oDimStyleProperties["DIMEXO"]);
200
95.8k
    const bool bWantExtLine1 = atoi(oDimStyleProperties["DIMSE1"]) == 0;
201
95.8k
    const bool bWantExtLine2 = atoi(oDimStyleProperties["DIMSE2"]) == 0;
202
95.8k
    const double dfTextHeight = CPLAtof(oDimStyleProperties["DIMTXT"]);
203
95.8k
    const int nUnitsPrecision = atoi(oDimStyleProperties["DIMDEC"]);
204
95.8k
    const bool bTextSupposedlyCentered =
205
95.8k
        atoi(oDimStyleProperties["DIMTAD"]) == 0;
206
95.8k
    CPLString osTextColor = oDimStyleProperties["DIMCLRT"];
207
208
    /*************************************************************************
209
210
       DIMENSION geometry layout
211
212
                      (11,21)(text center point)
213
            |          DimText                  |
214
    (10,20) X<--------------------------------->X (Arrow2 - computed)
215
    (Arrow1)|                                   |
216
            |                                   |
217
            |                                   X (13,23) (Target2)
218
            |
219
            X (14,24) (Target1)
220
221
    Given:
222
      Locations Arrow1, Target1, and Target2 we need to compute Arrow2.
223
224
    Steps:
225
     1) Compute direction vector from Target1 to Arrow1 (Vec1).
226
     2) Compute direction vector for arrow as perpendicular to Vec1 (call Vec2).
227
     3) Compute Arrow2 location as intersection between line defined by
228
        Vec2 and Arrow1 and line defined by Target2 and direction Vec1 (call
229
    Arrow2)
230
231
    Then we can draw lines for the various components.
232
233
    Note that Vec1 and Vec2 may be horizontal, vertical or on an angle but
234
    the approach is as above in all these cases.
235
236
    *************************************************************************/
237
238
    /* -------------------------------------------------------------------- */
239
    /*      Step 1, compute direction vector between Target1 and Arrow1.    */
240
    /* -------------------------------------------------------------------- */
241
95.8k
    double dfVec1X = dfArrowX1 - dfTargetX1;
242
95.8k
    double dfVec1Y = dfArrowY1 - dfTargetY1;
243
244
    // make Vec1 a unit vector
245
95.8k
    double dfVec1Length = PointDist(0, 0, dfVec1X, dfVec1Y);
246
95.8k
    if (dfVec1Length > 0.0)
247
18.3k
    {
248
18.3k
        dfVec1X /= dfVec1Length;
249
18.3k
        dfVec1Y /= dfVec1Length;
250
18.3k
    }
251
252
    /* -------------------------------------------------------------------- */
253
    /*      Step 2, compute the direction vector from Arrow1 to Arrow2      */
254
    /*      as a perpendicular to Vec1.                                     */
255
    /* -------------------------------------------------------------------- */
256
95.8k
    double dfVec2X = dfVec1Y;
257
95.8k
    double dfVec2Y = -dfVec1X;
258
259
    /* -------------------------------------------------------------------- */
260
    /*      Step 3, compute intersection of line from target2 along         */
261
    /*      direction vector 1, with the line through Arrow1 and            */
262
    /*      direction vector 2.                                             */
263
    /* -------------------------------------------------------------------- */
264
95.8k
    double dfArrowX2 = 0.0;
265
95.8k
    double dfArrowY2 = 0.0;
266
267
    // special case if vec1 is zero, which means the arrow and target
268
    // points coincide.
269
95.8k
    if (dfVec1X == 0.0 && dfVec1Y == 0.0)
270
77.5k
    {
271
77.5k
        dfArrowX2 = dfTargetX2;
272
77.5k
        dfArrowY2 = dfTargetY2;
273
77.5k
    }
274
275
    // special case if vec1 is vertical.
276
18.3k
    else if (dfVec1X == 0.0)
277
5.89k
    {
278
5.89k
        dfArrowX2 = dfTargetX2;
279
5.89k
        dfArrowY2 = dfArrowY1;
280
5.89k
    }
281
282
    // special case if vec1 is horizontal.
283
12.4k
    else if (dfVec1Y == 0.0)
284
8.85k
    {
285
8.85k
        dfArrowX2 = dfArrowX1;
286
8.85k
        dfArrowY2 = dfTargetY2;
287
8.85k
    }
288
289
3.57k
    else  // General case for diagonal vectors.
290
3.57k
    {
291
        // first convert vec1 + target2 into y = mx + b format: call this L1
292
293
3.57k
        const double dfL1M = dfVec1Y / dfVec1X;
294
3.57k
        const double dfL1B = dfTargetY2 - dfL1M * dfTargetX2;
295
296
        // convert vec2 + Arrow1 into y = mx + b format, call this L2
297
298
3.57k
        const double dfL2M = dfVec2Y / dfVec2X;
299
3.57k
        const double dfL2B = dfArrowY1 - dfL2M * dfArrowX1;
300
301
        // Compute intersection x = (b2-b1) / (m1-m2)
302
303
3.57k
        dfArrowX2 = (dfL2B - dfL1B) / (dfL1M - dfL2M);
304
3.57k
        dfArrowY2 = dfL2M * dfArrowX2 + dfL2B;
305
3.57k
    }
306
307
    /* -------------------------------------------------------------------- */
308
    /*      Create geometries for the different components of the           */
309
    /*      dimension object.                                               */
310
    /* -------------------------------------------------------------------- */
311
95.8k
    OGRMultiLineString *poMLS = new OGRMultiLineString();
312
95.8k
    OGRLineString oLine;
313
314
    // Main arrow line between Arrow1 and Arrow2.
315
95.8k
    oLine.setPoint(0, dfArrowX1, dfArrowY1);
316
95.8k
    oLine.setPoint(1, dfArrowX2, dfArrowY2);
317
95.8k
    poMLS->addGeometry(&oLine);
318
319
    // Insert default arrowheads.
320
95.8k
    InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale);
321
95.8k
    InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale, true);
322
323
    // Dimension line from Target1 to Arrow1 with a small extension.
324
95.8k
    oLine.setPoint(0, dfTargetX1 + dfVec1X * dfExtLineOffset,
325
95.8k
                   dfTargetY1 + dfVec1Y * dfExtLineOffset);
326
95.8k
    oLine.setPoint(1, dfArrowX1 + dfVec1X * dfExtLineExtendLength,
327
95.8k
                   dfArrowY1 + dfVec1Y * dfExtLineExtendLength);
328
95.8k
    if (bWantExtLine1 && oLine.get_Length() > 0.0)
329
17.9k
    {
330
17.9k
        poMLS->addGeometry(&oLine);
331
17.9k
    }
332
333
    // Dimension line from Target2 to Arrow2 with a small extension.
334
95.8k
    oLine.setPoint(0, dfTargetX2 + dfVec1X * dfExtLineOffset,
335
95.8k
                   dfTargetY2 + dfVec1Y * dfExtLineOffset);
336
95.8k
    oLine.setPoint(1, dfArrowX2 + dfVec1X * dfExtLineExtendLength,
337
95.8k
                   dfArrowY2 + dfVec1Y * dfExtLineExtendLength);
338
95.8k
    if (bWantExtLine2 && oLine.get_Length() > 0.0)
339
17.9k
    {
340
17.9k
        poMLS->addGeometry(&oLine);
341
17.9k
    }
342
343
95.8k
    poFeature->SetGeometryDirectly(poMLS);
344
345
95.8k
    PrepareLineStyle(poFeature);
346
347
    /* -------------------------------------------------------------------- */
348
    /*      Prepare a new feature to serve as the dimension text label      */
349
    /*      feature.  We will push it onto the layer as a pending           */
350
    /*      feature for the next feature read.                              */
351
    /*                                                                      */
352
    /*      The DXF format supports a myriad of options for dimension       */
353
    /*      text placement, some of which involve the drawing of            */
354
    /*      additional lines and the like.  For now we ignore most of       */
355
    /*      those properties and place the text alongside the dimension     */
356
    /*      line.                                                           */
357
    /* -------------------------------------------------------------------- */
358
359
    // a single space suppresses labeling.
360
95.8k
    if (osText == " ")
361
2.00k
        return poFeature;
362
363
93.8k
    OGRDXFFeature *poLabelFeature = poFeature->CloneDXFFeature();
364
365
93.8k
    poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
366
367
93.8k
    if (osText.empty())
368
58.5k
        osText = "<>";
369
370
    // Do we need to compute the dimension value?
371
93.8k
    size_t nDimensionPos = osText.find("<>");
372
93.8k
    if (nDimensionPos == std::string::npos)
373
34.3k
    {
374
34.3k
        poLabelFeature->SetField("Text", TextUnescape(osText, true));
375
34.3k
    }
376
59.5k
    else
377
59.5k
    {
378
        // Replace the first occurrence of <> with the dimension
379
59.5k
        CPLString osDimensionText;
380
59.5k
        FormatDimension(osDimensionText,
381
59.5k
                        PointDist(dfArrowX1, dfArrowY1, dfArrowX2, dfArrowY2),
382
59.5k
                        nUnitsPrecision);
383
59.5k
        osText.replace(nDimensionPos, 2, osDimensionText);
384
59.5k
        poLabelFeature->SetField("Text", TextUnescape(osText, true));
385
59.5k
    }
386
387
93.8k
    CPLString osStyle;
388
93.8k
    char szBuffer[64];
389
390
93.8k
    osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",
391
93.8k
                   TextUnescape(osText.c_str(), true).c_str());
392
393
    // If the text is supposed to be centered on the line, we align
394
    // it above the line. Drawing it properly would require us to
395
    // work out the width of the text, which seems like too much
396
    // effort for what is just a fallback renderer.
397
93.8k
    if (bTextSupposedlyCentered)
398
93.7k
        osStyle += ",p:11";
399
139
    else
400
139
        osStyle += ",p:5";
401
402
    // Compute the text angle. Use atan to avoid upside-down text
403
93.8k
    const double dfTextAngle =
404
93.8k
        (dfArrowX1 == dfArrowX2)
405
93.8k
            ? -90.0
406
93.8k
            : atan((dfArrowY1 - dfArrowY2) / (dfArrowX1 - dfArrowX2)) * 180.0 /
407
11.0k
                  M_PI;
408
409
93.8k
    if (dfTextAngle != 0.0)
410
85.8k
    {
411
85.8k
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
412
85.8k
        osStyle += CPLString().Printf(",a:%s", szBuffer);
413
85.8k
    }
414
415
93.8k
    if (dfTextHeight != 0.0)
416
92.4k
    {
417
92.4k
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight * dfScale);
418
92.4k
        osStyle += CPLString().Printf(",s:%sg", szBuffer);
419
92.4k
    }
420
421
93.8k
    poLabelFeature->oStyleProperties["Color"] = std::move(osTextColor);
422
93.8k
    osStyle += ",c:";
423
93.8k
    osStyle += poLabelFeature->GetColor(poDS, poFeature);
424
425
93.8k
    osStyle += ")";
426
427
93.8k
    poLabelFeature->SetStyleString(osStyle);
428
429
93.8k
    apoPendingFeatures.push(poLabelFeature);
430
431
93.8k
    return poFeature;
432
95.8k
}
433
434
/************************************************************************/
435
/*                          FormatDimension()                           */
436
/*                                                                      */
437
/*      Format a dimension number according to the current files        */
438
/*      formatting conventions.                                         */
439
/************************************************************************/
440
441
void OGRDXFLayer::FormatDimension(CPLString &osText, const double dfValue,
442
                                  int nPrecision)
443
444
59.5k
{
445
59.5k
    if (nPrecision < 0)
446
288
        nPrecision = 0;
447
59.2k
    else if (nPrecision > 20)
448
797
        nPrecision = 20;
449
450
    // We could do a significantly more precise formatting if we want
451
    // to spend the effort.  See QCAD's rs_dimlinear.cpp and related files
452
    // for example.
453
454
59.5k
    char szFormat[32];
455
59.5k
    snprintf(szFormat, sizeof(szFormat), "%%.%df", nPrecision);
456
457
59.5k
    char szBuffer[64];
458
59.5k
    CPLsnprintf(szBuffer, sizeof(szBuffer), szFormat, dfValue);
459
460
59.5k
    osText = szBuffer;
461
59.5k
}