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/ogrdxf_dimension.cpp
Line
Count
Source
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
176k
{
30
176k
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
31
176k
}
32
#endif
33
34
/************************************************************************/
35
/*                         TranslateDIMENSION()                         */
36
/************************************************************************/
37
38
OGRDXFFeature *OGRDXFLayer::TranslateDIMENSION()
39
40
110k
{
41
110k
    char szLineBuf[257];
42
110k
    int nCode = 0;
43
    // int  nDimType = 0;
44
110k
    OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
45
110k
    double dfArrowX1 = 0.0;
46
110k
    double dfArrowY1 = 0.0;
47
    // double dfArrowZ1 = 0.0;
48
110k
    double dfTargetX1 = 0.0;
49
110k
    double dfTargetY1 = 0.0;
50
    // double dfTargetZ1 = 0.0;
51
110k
    double dfTargetX2 = 0.0;
52
110k
    double dfTargetY2 = 0.0;
53
    // double dfTargetZ2 = 0.0;
54
110k
    double dfTextX = 0.0;
55
110k
    double dfTextY = 0.0;
56
    // double dfTextZ = 0.0;
57
58
110k
    bool bReadyForDimstyleOverride = false;
59
60
110k
    bool bHaveBlock = false;
61
110k
    CPLString osBlockName;
62
110k
    CPLString osText;
63
64
110k
    std::map<CPLString, CPLString> oDimStyleProperties;
65
110k
    poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
66
67
762k
    while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
68
652k
    {
69
652k
        switch (nCode)
70
652k
        {
71
65.7k
            case 2:
72
65.7k
                bHaveBlock = true;
73
65.7k
                osBlockName = szLineBuf;
74
65.7k
                break;
75
76
37.8k
            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
37.8k
                poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
80
37.8k
                break;
81
82
27.6k
            case 10:
83
27.6k
                dfArrowX1 = CPLAtof(szLineBuf);
84
27.6k
                break;
85
86
22.3k
            case 20:
87
22.3k
                dfArrowY1 = CPLAtof(szLineBuf);
88
22.3k
                break;
89
90
19.8k
            case 30:
91
                /* dfArrowZ1 = CPLAtof(szLineBuf); */
92
19.8k
                break;
93
94
7.95k
            case 11:
95
7.95k
                dfTextX = CPLAtof(szLineBuf);
96
7.95k
                break;
97
98
24.2k
            case 21:
99
24.2k
                dfTextY = CPLAtof(szLineBuf);
100
24.2k
                break;
101
102
8.75k
            case 31:
103
                /* dfTextZ = CPLAtof(szLineBuf); */
104
8.75k
                break;
105
106
15.0k
            case 13:
107
15.0k
                dfTargetX2 = CPLAtof(szLineBuf);
108
15.0k
                break;
109
110
23.1k
            case 23:
111
23.1k
                dfTargetY2 = CPLAtof(szLineBuf);
112
23.1k
                break;
113
114
3.75k
            case 33:
115
                /* dfTargetZ2 = CPLAtof(szLineBuf); */
116
3.75k
                break;
117
118
5.24k
            case 14:
119
5.24k
                dfTargetX1 = CPLAtof(szLineBuf);
120
5.24k
                break;
121
122
22.8k
            case 24:
123
22.8k
                dfTargetY1 = CPLAtof(szLineBuf);
124
22.8k
                break;
125
126
5.00k
            case 34:
127
                /* dfTargetZ1 = CPLAtof(szLineBuf); */
128
5.00k
                break;
129
130
2.31k
            case 70:
131
                /* nDimType = atoi(szLineBuf); */
132
2.31k
                break;
133
134
76.4k
            case 1:
135
76.4k
                osText = szLineBuf;
136
76.4k
                break;
137
138
7.31k
            case 1001:
139
7.31k
                bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
140
7.31k
                break;
141
142
12.0k
            case 1070:
143
12.0k
                if (bReadyForDimstyleOverride)
144
9.20k
                {
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
9.20k
                    const int nInnerCode = atoi(szLineBuf);
149
9.20k
                    const char *pszProperty =
150
9.20k
                        ACGetDimStylePropertyName(nInnerCode);
151
9.20k
                    if (pszProperty)
152
7.55k
                    {
153
7.55k
                        nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
154
7.55k
                        if (nCode == 1005 || nCode == 1040 || nCode == 1070)
155
6.37k
                            oDimStyleProperties[pszProperty] = szLineBuf;
156
7.55k
                    }
157
9.20k
                }
158
12.0k
                break;
159
160
264k
            default:
161
264k
                TranslateGenericProperty(poFeature, nCode, szLineBuf);
162
264k
                break;
163
652k
        }
164
652k
    }
165
110k
    if (nCode < 0)
166
2.04k
    {
167
2.04k
        DXF_LAYER_READER_ERROR();
168
2.04k
        delete poFeature;
169
2.04k
        return nullptr;
170
2.04k
    }
171
108k
    if (nCode == 0)
172
108k
        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
108k
    if (bHaveBlock && osBlockName.length() > 0)
179
35.8k
    {
180
        // Always inline the block, because this is an anonymous block that the
181
        // user likely doesn't know or care about
182
35.8k
        try
183
35.8k
        {
184
35.8k
            OGRDXFFeature *poBlockFeature = InsertBlockInline(
185
35.8k
                CPLGetErrorCounter(), osBlockName, OGRDXFInsertTransformer(),
186
35.8k
                poFeature, apoPendingFeatures, true, false);
187
188
35.8k
            return poBlockFeature;  // may be NULL but that is OK
189
35.8k
        }
190
35.8k
        catch (const std::invalid_argument &)
191
35.8k
        {
192
35.1k
        }
193
35.8k
    }
194
195
    // Unpack the dimension style
196
107k
    const double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
197
107k
    const double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
198
107k
    const double dfExtLineExtendLength = CPLAtof(oDimStyleProperties["DIMEXE"]);
199
107k
    const double dfExtLineOffset = CPLAtof(oDimStyleProperties["DIMEXO"]);
200
107k
    const bool bWantExtLine1 = atoi(oDimStyleProperties["DIMSE1"]) == 0;
201
107k
    const bool bWantExtLine2 = atoi(oDimStyleProperties["DIMSE2"]) == 0;
202
107k
    const double dfTextHeight = CPLAtof(oDimStyleProperties["DIMTXT"]);
203
107k
    const int nUnitsPrecision = atoi(oDimStyleProperties["DIMDEC"]);
204
107k
    const bool bTextSupposedlyCentered =
205
107k
        atoi(oDimStyleProperties["DIMTAD"]) == 0;
206
107k
    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
107k
    double dfVec1X = dfArrowX1 - dfTargetX1;
242
107k
    double dfVec1Y = dfArrowY1 - dfTargetY1;
243
244
    // make Vec1 a unit vector
245
107k
    double dfVec1Length = PointDist(0, 0, dfVec1X, dfVec1Y);
246
107k
    if (dfVec1Length > 0.0)
247
20.9k
    {
248
20.9k
        dfVec1X /= dfVec1Length;
249
20.9k
        dfVec1Y /= dfVec1Length;
250
20.9k
    }
251
252
    /* -------------------------------------------------------------------- */
253
    /*      Step 2, compute the direction vector from Arrow1 to Arrow2      */
254
    /*      as a perpendicular to Vec1.                                     */
255
    /* -------------------------------------------------------------------- */
256
107k
    double dfVec2X = dfVec1Y;
257
107k
    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
107k
    double dfArrowX2 = 0.0;
265
107k
    double dfArrowY2 = 0.0;
266
267
    // special case if vec1 is zero, which means the arrow and target
268
    // points coincide.
269
107k
    if (dfVec1X == 0.0 && dfVec1Y == 0.0)
270
86.7k
    {
271
86.7k
        dfArrowX2 = dfTargetX2;
272
86.7k
        dfArrowY2 = dfTargetY2;
273
86.7k
    }
274
275
    // special case if vec1 is vertical.
276
20.9k
    else if (dfVec1X == 0.0)
277
6.96k
    {
278
6.96k
        dfArrowX2 = dfTargetX2;
279
6.96k
        dfArrowY2 = dfArrowY1;
280
6.96k
    }
281
282
    // special case if vec1 is horizontal.
283
14.0k
    else if (dfVec1Y == 0.0)
284
10.5k
    {
285
10.5k
        dfArrowX2 = dfArrowX1;
286
10.5k
        dfArrowY2 = dfTargetY2;
287
10.5k
    }
288
289
3.50k
    else  // General case for diagonal vectors.
290
3.50k
    {
291
        // first convert vec1 + target2 into y = mx + b format: call this L1
292
293
3.50k
        const double dfL1M = dfVec1Y / dfVec1X;
294
3.50k
        const double dfL1B = dfTargetY2 - dfL1M * dfTargetX2;
295
296
        // convert vec2 + Arrow1 into y = mx + b format, call this L2
297
298
3.50k
        const double dfL2M = dfVec2Y / dfVec2X;
299
3.50k
        const double dfL2B = dfArrowY1 - dfL2M * dfArrowX1;
300
301
        // Compute intersection x = (b2-b1) / (m1-m2)
302
303
3.50k
        dfArrowX2 = (dfL2B - dfL1B) / (dfL1M - dfL2M);
304
3.50k
        dfArrowY2 = dfL2M * dfArrowX2 + dfL2B;
305
3.50k
    }
306
307
    /* -------------------------------------------------------------------- */
308
    /*      Create geometries for the different components of the           */
309
    /*      dimension object.                                               */
310
    /* -------------------------------------------------------------------- */
311
107k
    OGRMultiLineString *poMLS = new OGRMultiLineString();
312
107k
    OGRLineString oLine;
313
314
    // Main arrow line between Arrow1 and Arrow2.
315
107k
    oLine.setPoint(0, dfArrowX1, dfArrowY1);
316
107k
    oLine.setPoint(1, dfArrowX2, dfArrowY2);
317
107k
    poMLS->addGeometry(&oLine);
318
319
    // Insert default arrowheads.
320
107k
    InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale);
321
107k
    InsertArrowhead(poFeature, "", &oLine, dfArrowheadSize * dfScale, true);
322
323
    // Dimension line from Target1 to Arrow1 with a small extension.
324
107k
    oLine.setPoint(0, dfTargetX1 + dfVec1X * dfExtLineOffset,
325
107k
                   dfTargetY1 + dfVec1Y * dfExtLineOffset);
326
107k
    oLine.setPoint(1, dfArrowX1 + dfVec1X * dfExtLineExtendLength,
327
107k
                   dfArrowY1 + dfVec1Y * dfExtLineExtendLength);
328
107k
    if (bWantExtLine1 && oLine.get_Length() > 0.0)
329
20.7k
    {
330
20.7k
        poMLS->addGeometry(&oLine);
331
20.7k
    }
332
333
    // Dimension line from Target2 to Arrow2 with a small extension.
334
107k
    oLine.setPoint(0, dfTargetX2 + dfVec1X * dfExtLineOffset,
335
107k
                   dfTargetY2 + dfVec1Y * dfExtLineOffset);
336
107k
    oLine.setPoint(1, dfArrowX2 + dfVec1X * dfExtLineExtendLength,
337
107k
                   dfArrowY2 + dfVec1Y * dfExtLineExtendLength);
338
107k
    if (bWantExtLine2 && oLine.get_Length() > 0.0)
339
20.5k
    {
340
20.5k
        poMLS->addGeometry(&oLine);
341
20.5k
    }
342
343
107k
    poFeature->SetGeometryDirectly(poMLS);
344
345
107k
    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
107k
    if (osText == " ")
361
2.80k
        return poFeature;
362
363
104k
    OGRDXFFeature *poLabelFeature = poFeature->CloneDXFFeature();
364
365
104k
    poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
366
367
104k
    if (osText.empty())
368
67.2k
        osText = "<>";
369
370
    // Do we need to compute the dimension value?
371
104k
    size_t nDimensionPos = osText.find("<>");
372
104k
    if (nDimensionPos == std::string::npos)
373
36.7k
    {
374
36.7k
        poLabelFeature->SetField("Text", TextUnescape(osText, true));
375
36.7k
    }
376
68.2k
    else
377
68.2k
    {
378
        // Replace the first occurrence of <> with the dimension
379
68.2k
        CPLString osDimensionText;
380
68.2k
        FormatDimension(osDimensionText,
381
68.2k
                        PointDist(dfArrowX1, dfArrowY1, dfArrowX2, dfArrowY2),
382
68.2k
                        nUnitsPrecision);
383
68.2k
        osText.replace(nDimensionPos, 2, osDimensionText);
384
68.2k
        poLabelFeature->SetField("Text", TextUnescape(osText, true));
385
68.2k
    }
386
387
104k
    CPLString osStyle;
388
104k
    char szBuffer[64];
389
390
104k
    osStyle.Printf("LABEL(f:\"Arial\",t:\"%s\"",
391
104k
                   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
104k
    if (bTextSupposedlyCentered)
398
104k
        osStyle += ",p:11";
399
89
    else
400
89
        osStyle += ",p:5";
401
402
    // Compute the text angle. Use atan to avoid upside-down text
403
104k
    const double dfTextAngle =
404
104k
        (dfArrowX1 == dfArrowX2)
405
104k
            ? -90.0
406
104k
            : atan((dfArrowY1 - dfArrowY2) / (dfArrowX1 - dfArrowX2)) * 180.0 /
407
12.5k
                  M_PI;
408
409
104k
    if (dfTextAngle != 0.0)
410
95.3k
    {
411
95.3k
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
412
95.3k
        osStyle += CPLString().Printf(",a:%s", szBuffer);
413
95.3k
    }
414
415
104k
    if (dfTextHeight != 0.0)
416
104k
    {
417
104k
        CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight * dfScale);
418
104k
        osStyle += CPLString().Printf(",s:%sg", szBuffer);
419
104k
    }
420
421
104k
    poLabelFeature->oStyleProperties["Color"] = std::move(osTextColor);
422
104k
    osStyle += ",c:";
423
104k
    osStyle += poLabelFeature->GetColor(poDS, poFeature);
424
425
104k
    osStyle += ")";
426
427
104k
    poLabelFeature->SetStyleString(osStyle);
428
429
104k
    apoPendingFeatures.push(poLabelFeature);
430
431
104k
    return poFeature;
432
107k
}
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
68.2k
{
445
68.2k
    if (nPrecision < 0)
446
415
        nPrecision = 0;
447
67.8k
    else if (nPrecision > 20)
448
406
        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
68.2k
    char szFormat[32];
455
68.2k
    snprintf(szFormat, sizeof(szFormat), "%%.%df", nPrecision);
456
457
68.2k
    char szBuffer[64];
458
68.2k
    CPLsnprintf(szBuffer, sizeof(szBuffer), szFormat, dfValue);
459
460
68.2k
    osText = szBuffer;
461
68.2k
}