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