LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/dxf - ogrdxf_leader.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 637 687 92.7 %
Date: 2024-11-21 22:18:42 Functions: 10 10 100.0 %

          Line data    Source code
       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          64 : inline static double PointDist(double x1, double y1, double x2, double y2)
      31             : {
      32          64 :     return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
      33             : }
      34             : #endif
      35             : 
      36          10 : inline static double PointDist(double x1, double y1, double z1, double x2,
      37             :                                double y2, double z2)
      38             : {
      39          10 :     return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) +
      40          10 :                 (z2 - z1) * (z2 - z1));
      41             : }
      42             : 
      43             : /************************************************************************/
      44             : /*                          TranslateLEADER()                           */
      45             : /************************************************************************/
      46             : 
      47          14 : OGRDXFFeature *OGRDXFLayer::TranslateLEADER()
      48             : 
      49             : {
      50             :     char szLineBuf[257];
      51             :     int nCode;
      52          14 :     OGRDXFFeature *poFeature = new OGRDXFFeature(poFeatureDefn);
      53             : 
      54          14 :     OGRLineString *poLine = new OGRLineString();
      55          14 :     bool bHaveX = false;
      56          14 :     bool bHaveY = false;
      57          14 :     bool bHaveZ = false;
      58          14 :     double dfCurrentX = 0.0;
      59          14 :     double dfCurrentY = 0.0;
      60          14 :     double dfCurrentZ = 0.0;
      61          14 :     int nNumVertices = 0;
      62             : 
      63          14 :     bool bHorizontalDirectionFlip = true;
      64          14 :     double dfHorizontalDirectionX = 1.0;
      65          14 :     double dfHorizontalDirectionY = 0.0;
      66          14 :     double dfHorizontalDirectionZ = 0.0;
      67          14 :     bool bHasTextAnnotation = false;
      68          14 :     double dfTextAnnotationWidth = 0.0;
      69          14 :     bool bIsSpline = false;
      70             : 
      71             :     // spec is silent as to default, but AutoCAD assumes true
      72          14 :     bool bWantArrowhead = true;
      73             : 
      74          14 :     bool bReadyForDimstyleOverride = false;
      75             : 
      76          28 :     std::map<CPLString, CPLString> oDimStyleProperties;
      77          14 :     poDS->PopulateDefaultDimStyleProperties(oDimStyleProperties);
      78             : 
      79         400 :     while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
      80             :     {
      81         386 :         switch (nCode)
      82             :         {
      83          14 :             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          14 :                 poDS->LookupDimStyle(szLineBuf, oDimStyleProperties);
      87          14 :                 break;
      88             : 
      89          52 :             case 10:
      90             :                 // add the previous point onto the linestring
      91          52 :                 if (bHaveX && bHaveY && bHaveZ)
      92             :                 {
      93          38 :                     poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
      94             :                                      dfCurrentZ);
      95          38 :                     bHaveY = bHaveZ = false;
      96             :                 }
      97          52 :                 dfCurrentX = CPLAtof(szLineBuf);
      98          52 :                 bHaveX = true;
      99          52 :                 break;
     100             : 
     101          52 :             case 20:
     102             :                 // add the previous point onto the linestring
     103          52 :                 if (bHaveX && bHaveY && bHaveZ)
     104             :                 {
     105           0 :                     poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
     106             :                                      dfCurrentZ);
     107           0 :                     bHaveX = bHaveZ = false;
     108             :                 }
     109          52 :                 dfCurrentY = CPLAtof(szLineBuf);
     110          52 :                 bHaveY = true;
     111          52 :                 break;
     112             : 
     113          52 :             case 30:
     114             :                 // add the previous point onto the linestring
     115          52 :                 if (bHaveX && bHaveY && bHaveZ)
     116             :                 {
     117           0 :                     poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY,
     118             :                                      dfCurrentZ);
     119           0 :                     bHaveX = bHaveY = false;
     120             :                 }
     121          52 :                 dfCurrentZ = CPLAtof(szLineBuf);
     122          52 :                 bHaveZ = true;
     123          52 :                 break;
     124             : 
     125           6 :             case 41:
     126           6 :                 dfTextAnnotationWidth = CPLAtof(szLineBuf);
     127           6 :                 break;
     128             : 
     129           2 :             case 71:
     130           2 :                 bWantArrowhead = atoi(szLineBuf) != 0;
     131           2 :                 break;
     132             : 
     133           2 :             case 72:
     134           2 :                 bIsSpline = atoi(szLineBuf) != 0;
     135           2 :                 break;
     136             : 
     137           6 :             case 73:
     138           6 :                 bHasTextAnnotation = atoi(szLineBuf) == 0;
     139           6 :                 break;
     140             : 
     141           4 :             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           4 :                 bHorizontalDirectionFlip = atoi(szLineBuf) != 0;
     145           4 :                 break;
     146             : 
     147           2 :             case 211:
     148           2 :                 dfHorizontalDirectionX = CPLAtof(szLineBuf);
     149           2 :                 break;
     150             : 
     151           2 :             case 221:
     152           2 :                 dfHorizontalDirectionY = CPLAtof(szLineBuf);
     153           2 :                 break;
     154             : 
     155           2 :             case 231:
     156           2 :                 dfHorizontalDirectionZ = CPLAtof(szLineBuf);
     157           2 :                 break;
     158             : 
     159          10 :             case 1001:
     160          10 :                 bReadyForDimstyleOverride = EQUAL(szLineBuf, "ACAD");
     161          10 :                 break;
     162             : 
     163          28 :             case 1070:
     164          28 :                 if (bReadyForDimstyleOverride)
     165             :                 {
     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          28 :                     const int nInnerCode = atoi(szLineBuf);
     170             :                     const char *pszProperty =
     171          28 :                         ACGetDimStylePropertyName(nInnerCode);
     172          28 :                     if (pszProperty)
     173             :                     {
     174          24 :                         nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf));
     175          24 :                         if (nCode == 1005 || nCode == 1040 || nCode == 1070)
     176          24 :                             oDimStyleProperties[pszProperty] = szLineBuf;
     177             :                     }
     178             :                 }
     179          28 :                 break;
     180             : 
     181         152 :             default:
     182         152 :                 TranslateGenericProperty(poFeature, nCode, szLineBuf);
     183         152 :                 break;
     184             :         }
     185             :     }
     186             : 
     187          14 :     if (nCode == 0)
     188          14 :         poDS->UnreadValue();
     189             : 
     190          14 :     if (bHaveX && bHaveY && bHaveZ)
     191          14 :         poLine->setPoint(nNumVertices++, dfCurrentX, dfCurrentY, dfCurrentZ);
     192             : 
     193             :     // Unpack the dimension style
     194          14 :     bool bWantExtension = atoi(oDimStyleProperties["DIMTAD"]) > 0;
     195          14 :     double dfTextOffset = CPLAtof(oDimStyleProperties["DIMGAP"]);
     196          14 :     double dfScale = CPLAtof(oDimStyleProperties["DIMSCALE"]);
     197          14 :     double dfArrowheadSize = CPLAtof(oDimStyleProperties["DIMASZ"]);
     198          14 :     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          14 :     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          14 :     if (dfScale == 0.0)
     206           0 :         dfScale = 1.0;
     207             : 
     208             :     // Use the color from the dimension style if it is not ByBlock
     209          14 :     if (nLeaderColor > 0)
     210           2 :         poFeature->oStyleProperties["Color"] = oDimStyleProperties["DIMCLRD"];
     211             : 
     212             :     /* -------------------------------------------------------------------- */
     213             :     /*      Add an arrowhead to the start of the leader line.               */
     214             :     /* -------------------------------------------------------------------- */
     215             : 
     216          14 :     if (bWantArrowhead && nNumVertices >= 2)
     217             :     {
     218          12 :         InsertArrowhead(poFeature, osArrowheadBlockHandle, poLine,
     219             :                         dfArrowheadSize * dfScale);
     220             :     }
     221             : 
     222          14 :     if (bHorizontalDirectionFlip)
     223             :     {
     224          10 :         dfHorizontalDirectionX *= -1;
     225          10 :         dfHorizontalDirectionX *= -1;
     226          10 :         dfHorizontalDirectionX *= -1;
     227             :     }
     228             : 
     229             :     /* -------------------------------------------------------------------- */
     230             :     /*      For a spline leader, determine the end tangent direction        */
     231             :     /*      and interpolate the spline vertices.                            */
     232             :     /* -------------------------------------------------------------------- */
     233             : 
     234          14 :     if (bIsSpline)
     235             :     {
     236           2 :         DXFTriple oEndTangent;
     237           2 :         if (bHasTextAnnotation)
     238             :         {
     239           0 :             oEndTangent =
     240           0 :                 DXFTriple(dfHorizontalDirectionX, dfHorizontalDirectionY,
     241             :                           dfHorizontalDirectionZ);
     242             :         }
     243           2 :         InterpolateSpline(poLine, oEndTangent);
     244             :     }
     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          14 :     if (bWantExtension && bHasTextAnnotation && poLine->getNumPoints() >= 2)
     257             :     {
     258           8 :         OGRPoint oLastVertex;
     259           4 :         poLine->getPoint(poLine->getNumPoints() - 1, &oLastVertex);
     260             : 
     261           4 :         double dfExtensionX = oLastVertex.getX();
     262           4 :         double dfExtensionY = oLastVertex.getY();
     263           4 :         double dfExtensionZ = oLastVertex.getZ();
     264             : 
     265           4 :         double dfExtensionLength =
     266           4 :             (dfTextOffset * dfScale) + dfTextAnnotationWidth;
     267           4 :         dfExtensionX += dfHorizontalDirectionX * dfExtensionLength;
     268           4 :         dfExtensionY += dfHorizontalDirectionY * dfExtensionLength;
     269           4 :         dfExtensionZ += dfHorizontalDirectionZ * dfExtensionLength;
     270             : 
     271           4 :         poLine->setPoint(poLine->getNumPoints(), dfExtensionX, dfExtensionY,
     272             :                          dfExtensionZ);
     273             :     }
     274             : 
     275          14 :     poFeature->SetGeometryDirectly(poLine);
     276             : 
     277          14 :     PrepareLineStyle(poFeature);
     278             : 
     279          28 :     return poFeature;
     280             : }
     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          34 :     DXFMLEADERVertex(double dfX, double dfY) : oCoords(DXFTriple(dfX, dfY, 0.0))
     292             :     {
     293          34 :     }
     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          18 : OGRDXFFeature *OGRDXFLayer::TranslateMLEADER()
     312             : 
     313             : {
     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             :     char szLineBuf[4096];
     317          18 :     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          18 :     OGRDXFFeature *poOverallFeature = new OGRDXFFeature(poFeatureDefn);
     322             : 
     323          36 :     DXFMLEADERLeader oLeader;
     324          36 :     std::vector<DXFMLEADERLeader> aoLeaders;
     325             : 
     326          36 :     std::vector<DXFMLEADERVertex> oLeaderLine;
     327          18 :     double dfCurrentX = 0.0;
     328          18 :     double dfCurrentY = 0.0;
     329          18 :     double dfCurrentX2 = 0.0;
     330          18 :     double dfCurrentY2 = 0.0;
     331          18 :     size_t nCurrentVertex = 0;
     332             : 
     333          18 :     double dfScale = 1.0;
     334          18 :     bool bHasDogleg = true;
     335          36 :     CPLString osLeaderColor = "0";
     336             : 
     337          36 :     CPLString osText;
     338          36 :     CPLString osTextStyleHandle;
     339          18 :     double dfTextX = 0.0;
     340          18 :     double dfTextY = 0.0;
     341          18 :     int nTextAlignment = 1;  // 1 = left, 2 = center, 3 = right
     342          18 :     double dfTextAngle = 0.0;
     343          18 :     double dfTextHeight = 4.0;
     344             : 
     345          36 :     CPLString osBlockHandle;
     346          18 :     OGRDXFInsertTransformer oBlockTransformer;
     347          36 :     CPLString osBlockAttributeHandle;
     348             :     // Map of ATTDEF handles to attribute text
     349          36 :     std::map<CPLString, CPLString> oBlockAttributes;
     350             : 
     351          36 :     CPLString osArrowheadBlockHandle;
     352          18 :     double dfArrowheadSize = 4.0;
     353             : 
     354             :     // The different leader line types
     355          18 :     const int MLT_NONE = 0;
     356          18 :     const int MLT_STRAIGHT = 1;
     357          18 :     const int MLT_SPLINE = 2;
     358          18 :     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          18 :     const int MLS_COMMON = 0;
     363          18 :     const int MLS_CONTEXT_DATA = 1;
     364          18 :     const int MLS_LEADER = 2;
     365          18 :     const int MLS_LEADER_LINE = 3;
     366          18 :     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        2210 :     while ((nCode = poDS->ReadValue(szLineBuf, sizeof(szLineBuf))) > 0)
     392             :     {
     393        2192 :         switch (nSection)
     394             :         {
     395         758 :             case MLS_COMMON:
     396             :                 switch (nCode)
     397             :                 {
     398          18 :                     case 300:
     399          18 :                         nSection = MLS_CONTEXT_DATA;
     400          18 :                         break;
     401             : 
     402           4 :                     case 342:
     403             :                         // 342 is the entity handle of the BLOCK_RECORD table
     404             :                         // entry that corresponds to the arrowhead block.
     405           4 :                         osArrowheadBlockHandle = szLineBuf;
     406           4 :                         break;
     407             : 
     408          18 :                     case 42:
     409             :                         // TODO figure out difference between 42 and 140 for
     410             :                         // arrowheadsize
     411          18 :                         dfArrowheadSize = CPLAtof(szLineBuf);
     412          18 :                         break;
     413             : 
     414          24 :                     case 330:
     415          24 :                         osBlockAttributeHandle = szLineBuf;
     416          24 :                         break;
     417             : 
     418           6 :                     case 302:
     419           6 :                         if (osBlockAttributeHandle != "")
     420             :                         {
     421           6 :                             oBlockAttributes[osBlockAttributeHandle] =
     422          12 :                                 TextUnescape(szLineBuf, true);
     423           6 :                             osBlockAttributeHandle = "";
     424             :                         }
     425           6 :                         break;
     426             : 
     427          18 :                     case 91:
     428          18 :                         osLeaderColor = szLineBuf;
     429          18 :                         break;
     430             : 
     431          18 :                     case 170:
     432          18 :                         nLeaderLineType = atoi(szLineBuf);
     433          18 :                         break;
     434             : 
     435          18 :                     case 291:
     436          18 :                         bHasDogleg = atoi(szLineBuf) != 0;
     437          18 :                         break;
     438             : 
     439         634 :                     default:
     440         634 :                         TranslateGenericProperty(poOverallFeature, nCode,
     441             :                                                  szLineBuf);
     442         634 :                         break;
     443             :                 }
     444         758 :                 break;
     445             : 
     446         956 :             case MLS_CONTEXT_DATA:
     447             :                 switch (nCode)
     448             :                 {
     449          18 :                     case 301:
     450          18 :                         nSection = MLS_COMMON;
     451          18 :                         break;
     452             : 
     453          20 :                     case 302:
     454          20 :                         nSection = MLS_LEADER;
     455          20 :                         break;
     456             : 
     457          10 :                     case 304:
     458          10 :                         osText = TextUnescape(szLineBuf, true);
     459          10 :                         break;
     460             : 
     461          18 :                     case 40:
     462          18 :                         dfScale = CPLAtof(szLineBuf);
     463          18 :                         break;
     464             : 
     465          10 :                     case 340:
     466             :                         // 340 is the entity handle of the STYLE table entry
     467             :                         // that corresponds to the text style.
     468          10 :                         osTextStyleHandle = szLineBuf;
     469          10 :                         break;
     470             : 
     471          10 :                     case 12:
     472          10 :                         dfTextX = CPLAtof(szLineBuf);
     473          10 :                         break;
     474             : 
     475          10 :                     case 22:
     476          10 :                         dfTextY = CPLAtof(szLineBuf);
     477          10 :                         break;
     478             : 
     479          18 :                     case 41:
     480          18 :                         dfTextHeight = CPLAtof(szLineBuf);
     481          18 :                         break;
     482             : 
     483          10 :                     case 42:
     484          10 :                         dfTextAngle = CPLAtof(szLineBuf) * 180 / M_PI;
     485          10 :                         break;
     486             : 
     487          10 :                     case 171:
     488          10 :                         nTextAlignment = atoi(szLineBuf);
     489          10 :                         break;
     490             : 
     491           6 :                     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           6 :                         osBlockHandle = szLineBuf;
     496           6 :                         break;
     497             : 
     498           6 :                     case 15:
     499           6 :                         oBlockTransformer.dfXOffset = CPLAtof(szLineBuf);
     500           6 :                         break;
     501             : 
     502           6 :                     case 25:
     503           6 :                         oBlockTransformer.dfYOffset = CPLAtof(szLineBuf);
     504           6 :                         break;
     505             : 
     506           6 :                     case 16:
     507           6 :                         oBlockTransformer.dfXScale = CPLAtof(szLineBuf);
     508           6 :                         break;
     509             : 
     510           6 :                     case 26:
     511           6 :                         oBlockTransformer.dfYScale = CPLAtof(szLineBuf);
     512           6 :                         break;
     513             : 
     514           6 :                     case 46:
     515           6 :                         oBlockTransformer.dfAngle = CPLAtof(szLineBuf);
     516           6 :                         break;
     517             :                 }
     518         956 :                 break;
     519             : 
     520         288 :             case MLS_LEADER:
     521             :                 switch (nCode)
     522             :                 {
     523          20 :                     case 303:
     524          20 :                         nSection = MLS_CONTEXT_DATA;
     525          20 :                         aoLeaders.emplace_back(std::move(oLeader));
     526          20 :                         oLeader = DXFMLEADERLeader();
     527          20 :                         break;
     528             : 
     529          24 :                     case 304:
     530          24 :                         nSection = MLS_LEADER_LINE;
     531          24 :                         break;
     532             : 
     533          20 :                     case 10:
     534          20 :                         oLeader.dfLandingX = CPLAtof(szLineBuf);
     535          20 :                         break;
     536             : 
     537          20 :                     case 20:
     538          20 :                         oLeader.dfLandingY = CPLAtof(szLineBuf);
     539          20 :                         break;
     540             : 
     541          20 :                     case 11:
     542          20 :                         oLeader.dfDoglegVectorX = CPLAtof(szLineBuf);
     543          20 :                         break;
     544             : 
     545          20 :                     case 21:
     546          20 :                         oLeader.dfDoglegVectorY = CPLAtof(szLineBuf);
     547          20 :                         break;
     548             : 
     549           4 :                     case 12:
     550           4 :                         dfCurrentX = CPLAtof(szLineBuf);
     551           4 :                         break;
     552             : 
     553           4 :                     case 22:
     554           4 :                         dfCurrentY = CPLAtof(szLineBuf);
     555           4 :                         break;
     556             : 
     557           4 :                     case 13:
     558           4 :                         dfCurrentX2 = CPLAtof(szLineBuf);
     559           4 :                         break;
     560             : 
     561           4 :                     case 23:
     562           4 :                         dfCurrentY2 = CPLAtof(szLineBuf);
     563           4 :                         oLeader.aoDoglegBreaks.push_back(std::make_pair(
     564           4 :                             DXFTriple(dfCurrentX, dfCurrentY, 0.0),
     565           8 :                             DXFTriple(dfCurrentX2, dfCurrentY2, 0.0)));
     566           4 :                         break;
     567             : 
     568          20 :                     case 40:
     569          20 :                         oLeader.dfDoglegLength = CPLAtof(szLineBuf);
     570          20 :                         break;
     571             :                 }
     572         288 :                 break;
     573             : 
     574         190 :             case MLS_LEADER_LINE:
     575             :                 switch (nCode)
     576             :                 {
     577          24 :                     case 305:
     578          24 :                         nSection = MLS_LEADER;
     579             :                         oLeader.aaoLeaderLines.emplace_back(
     580          24 :                             std::move(oLeaderLine));
     581          24 :                         oLeaderLine = std::vector<DXFMLEADERVertex>();
     582          24 :                         break;
     583             : 
     584          34 :                     case 10:
     585          34 :                         dfCurrentX = CPLAtof(szLineBuf);
     586          34 :                         break;
     587             : 
     588          34 :                     case 20:
     589          34 :                         dfCurrentY = CPLAtof(szLineBuf);
     590          34 :                         oLeaderLine.push_back(
     591          68 :                             DXFMLEADERVertex(dfCurrentX, dfCurrentY));
     592          34 :                         break;
     593             : 
     594           4 :                     case 90:
     595           4 :                         nCurrentVertex = atoi(szLineBuf);
     596           4 :                         if (nCurrentVertex >= oLeaderLine.size())
     597             :                         {
     598           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
     599             :                                      "Wrong group code 90 in LEADER_LINE: %s",
     600             :                                      szLineBuf);
     601           0 :                             DXF_LAYER_READER_ERROR();
     602           0 :                             delete poOverallFeature;
     603           0 :                             return nullptr;
     604             :                         }
     605           4 :                         break;
     606             : 
     607           6 :                     case 11:
     608           6 :                         dfCurrentX = CPLAtof(szLineBuf);
     609           6 :                         break;
     610             : 
     611           6 :                     case 21:
     612           6 :                         dfCurrentY = CPLAtof(szLineBuf);
     613           6 :                         break;
     614             : 
     615           6 :                     case 12:
     616           6 :                         dfCurrentX2 = CPLAtof(szLineBuf);
     617           6 :                         break;
     618             : 
     619           6 :                     case 22:
     620           6 :                         if (nCurrentVertex >= oLeaderLine.size())
     621             :                         {
     622           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
     623             :                                      "Misplaced group code 22 in LEADER_LINE");
     624           0 :                             DXF_LAYER_READER_ERROR();
     625           0 :                             delete poOverallFeature;
     626           0 :                             return nullptr;
     627             :                         }
     628           6 :                         dfCurrentY2 = CPLAtof(szLineBuf);
     629          12 :                         oLeaderLine[nCurrentVertex].aoBreaks.push_back(
     630           0 :                             std::make_pair(
     631           6 :                                 DXFTriple(dfCurrentX, dfCurrentY, 0.0),
     632          12 :                                 DXFTriple(dfCurrentX2, dfCurrentY2, 0.0)));
     633           6 :                         break;
     634             :                 }
     635         190 :                 break;
     636             :         }
     637             :     }
     638             : 
     639          18 :     if (nCode < 0)
     640             :     {
     641           0 :         DXF_LAYER_READER_ERROR();
     642           0 :         delete poOverallFeature;
     643           0 :         return nullptr;
     644             :     }
     645          18 :     if (nCode == 0)
     646          18 :         poDS->UnreadValue();
     647             : 
     648             :     // Convert the block handle to a block name. If there is no block,
     649             :     // osBlockName will remain empty.
     650          36 :     CPLString osBlockName;
     651             : 
     652          18 :     if (osBlockHandle != "")
     653           6 :         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          18 :     OGRDXFFeature *poLeaderFeature = poOverallFeature->CloneDXFFeature();
     660          18 :     poLeaderFeature->oStyleProperties["Color"] = osLeaderColor;
     661             : 
     662          18 :     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          18 :     OGRDXFFeature *poArrowheadOwningFeature = poLeaderFeature;
     668          18 :     if ((atoi(osLeaderColor) & 0xC2000000) == 0xC0000000)
     669          16 :         poArrowheadOwningFeature = poOverallFeature;
     670             : 
     671          38 :     for (std::vector<DXFMLEADERLeader>::iterator oIt = aoLeaders.begin();
     672          58 :          nLeaderLineType != MLT_NONE && oIt != aoLeaders.end(); ++oIt)
     673             :     {
     674             :         const bool bLeaderHasDogleg =
     675          16 :             bHasDogleg && nLeaderLineType != MLT_SPLINE &&
     676          50 :             oIt->dfDoglegLength != 0.0 &&
     677          14 :             (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             :         const double dfDoglegX =
     682          20 :             oIt->dfLandingX + oIt->dfDoglegVectorX * oIt->dfDoglegLength;
     683             :         const double dfDoglegY =
     684          20 :             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          20 :         if (!bHasDogleg || nLeaderLineType == MLT_SPLINE)
     690             :         {
     691           6 :             oIt->dfLandingX = dfDoglegX;
     692           6 :             oIt->dfLandingY = dfDoglegY;
     693             :         }
     694             : 
     695             :         // Iterate through each leader line
     696          44 :         for (const auto &aoLineVertices : oIt->aaoLeaderLines)
     697             :         {
     698          24 :             if (aoLineVertices.empty())
     699           0 :                 continue;
     700             : 
     701          24 :             OGRLineString *poLeaderLine = new OGRLineString();
     702             : 
     703             :             // Get the first line segment for arrowhead purposes
     704          24 :             poLeaderLine->addPoint(aoLineVertices[0].oCoords.dfX,
     705          24 :                                    aoLineVertices[0].oCoords.dfY);
     706             : 
     707          24 :             if (aoLineVertices.size() > 1)
     708             :             {
     709           6 :                 poLeaderLine->addPoint(aoLineVertices[1].oCoords.dfX,
     710           6 :                                        aoLineVertices[1].oCoords.dfY);
     711             :             }
     712             :             else
     713             :             {
     714          18 :                 poLeaderLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
     715             :             }
     716             : 
     717             :             // Add an arrowhead if required
     718          24 :             InsertArrowhead(poArrowheadOwningFeature, osArrowheadBlockHandle,
     719             :                             poLeaderLine, dfArrowheadSize * dfScale);
     720             : 
     721          24 :             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          58 :             for (size_t iVertex = 0; iVertex < aoLineVertices.size(); iVertex++)
     726             :             {
     727          34 :                 if (iVertex > 0)
     728             :                 {
     729          10 :                     poLeaderLine->addPoint(aoLineVertices[iVertex].oCoords.dfX,
     730          10 :                                            aoLineVertices[iVertex].oCoords.dfY);
     731             :                 }
     732             : 
     733             :                 // Breaks are ignored for spline leaders
     734          34 :                 if (nLeaderLineType != MLT_SPLINE)
     735             :                 {
     736          34 :                     for (const auto &oBreak : aoLineVertices[iVertex].aoBreaks)
     737             :                     {
     738           6 :                         poLeaderLine->addPoint(oBreak.first.dfX,
     739           6 :                                                oBreak.first.dfY);
     740             : 
     741           6 :                         poMLS->addGeometryDirectly(poLeaderLine);
     742           6 :                         poLeaderLine = new OGRLineString();
     743             : 
     744           6 :                         poLeaderLine->addPoint(oBreak.second.dfX,
     745           6 :                                                oBreak.second.dfY);
     746             :                     }
     747             :                 }
     748             :             }
     749             : 
     750             :             // Add the final vertex (the landing) to the end of the line
     751          24 :             poLeaderLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
     752             : 
     753             :             // Make the spline geometry for spline leaders
     754          24 :             if (nLeaderLineType == MLT_SPLINE)
     755             :             {
     756           4 :                 DXFTriple oEndTangent;
     757           4 :                 if (osBlockName.empty())
     758             :                 {
     759           4 :                     oEndTangent = DXFTriple(oIt->dfDoglegVectorX,
     760           4 :                                             oIt->dfDoglegVectorY, 0);
     761             :                 }
     762           4 :                 InterpolateSpline(poLeaderLine, oEndTangent);
     763             :             }
     764             : 
     765          24 :             poMLS->addGeometryDirectly(poLeaderLine);
     766             :         }
     767             : 
     768             :         // Add the dogleg as a separate line in the MLS
     769          20 :         if (bLeaderHasDogleg)
     770             :         {
     771          14 :             OGRLineString *poDoglegLine = new OGRLineString();
     772          14 :             poDoglegLine->addPoint(oIt->dfLandingX, oIt->dfLandingY);
     773             : 
     774             :             // Interrupt the dogleg line at breaks
     775          18 :             for (const auto &oBreak : oIt->aoDoglegBreaks)
     776             :             {
     777           4 :                 poDoglegLine->addPoint(oBreak.first.dfX, oBreak.first.dfY);
     778             : 
     779           4 :                 poMLS->addGeometryDirectly(poDoglegLine);
     780           4 :                 poDoglegLine = new OGRLineString();
     781             : 
     782           4 :                 poDoglegLine->addPoint(oBreak.second.dfX, oBreak.second.dfY);
     783             :             }
     784             : 
     785          14 :             poDoglegLine->addPoint(dfDoglegX, dfDoglegY);
     786          14 :             poMLS->addGeometryDirectly(poDoglegLine);
     787             :         }
     788             :     }
     789             : 
     790          18 :     poLeaderFeature->SetGeometryDirectly(poMLS);
     791             : 
     792          18 :     PrepareLineStyle(poLeaderFeature, poOverallFeature);
     793             : 
     794             :     /* -------------------------------------------------------------------- */
     795             :     /*      If we have block content, insert that block.                    */
     796             :     /* -------------------------------------------------------------------- */
     797             : 
     798          18 :     if (osBlockName != "")
     799             :     {
     800           6 :         oBlockTransformer.dfXScale *= dfScale;
     801           6 :         oBlockTransformer.dfYScale *= dfScale;
     802             : 
     803           6 :         DXFBlockDefinition *poBlock = poDS->LookupBlock(osBlockName);
     804             : 
     805          12 :         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          12 :         if (poBlock && !oBlockAttributes.empty() &&
     811           6 :             (poDS->InlineBlocks() ||
     812           0 :              poOverallFeature->GetFieldIndex("BlockAttributes") != -1))
     813             :         {
     814          12 :             for (std::vector<OGRDXFFeature *>::iterator oIt =
     815           6 :                      poBlock->apoFeatures.begin();
     816          30 :                  oIt != poBlock->apoFeatures.end(); ++oIt)
     817             :             {
     818             :                 const char *pszHandle =
     819          12 :                     (*oIt)->GetFieldAsString("EntityHandle");
     820             : 
     821          12 :                 if (pszHandle && oBlockAttributes.count(pszHandle) > 0)
     822           6 :                     oBlockAttributeValues[*oIt] = oBlockAttributes[pszHandle];
     823             :             }
     824             :         }
     825             : 
     826           6 :         OGRDXFFeature *poBlockFeature = poOverallFeature->CloneDXFFeature();
     827             : 
     828             :         // If not inlining the block, insert a reference and add attributes
     829             :         // to this feature.
     830           6 :         if (!poDS->InlineBlocks())
     831             :         {
     832           0 :             poBlockFeature = InsertBlockReference(
     833             :                 osBlockName, oBlockTransformer, poBlockFeature);
     834             : 
     835           0 :             if (!oBlockAttributes.empty() &&
     836           0 :                 poOverallFeature->GetFieldIndex("BlockAttributes") != -1)
     837             :             {
     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             :                 {
     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             :                 }
     855             : 
     856           0 :                 apszAttribs.push_back(nullptr);
     857             : 
     858           0 :                 poBlockFeature->SetField("BlockAttributes", &apszAttribs[0]);
     859             :             }
     860             : 
     861           0 :             apoPendingFeatures.push(poBlockFeature);
     862             :         }
     863             :         else
     864             :         {
     865             :             // Insert the block inline.
     866          12 :             OGRDXFFeatureQueue apoExtraFeatures;
     867             :             try
     868             :             {
     869           6 :                 poBlockFeature = InsertBlockInline(
     870             :                     CPLGetErrorCounter(), osBlockName, oBlockTransformer,
     871             :                     poBlockFeature, apoExtraFeatures, true,
     872           6 :                     poDS->ShouldMergeBlockGeometries());
     873             :             }
     874           0 :             catch (const std::invalid_argument &)
     875             :             {
     876             :                 // Block doesn't exist
     877           0 :                 delete poBlockFeature;
     878           0 :                 poBlockFeature = nullptr;
     879             :             }
     880             : 
     881             :             // Add the block geometries to the pending feature stack.
     882           6 :             if (poBlockFeature)
     883             :             {
     884           6 :                 apoPendingFeatures.push(poBlockFeature);
     885             :             }
     886           6 :             while (!apoExtraFeatures.empty())
     887             :             {
     888           0 :                 apoPendingFeatures.push(apoExtraFeatures.front());
     889           0 :                 apoExtraFeatures.pop();
     890             :             }
     891             : 
     892             :             // Also add any attributes to the pending feature stack.
     893           6 :             for (std::map<OGRDXFFeature *, CPLString>::iterator oIt =
     894           6 :                      oBlockAttributeValues.begin();
     895          18 :                  oIt != oBlockAttributeValues.end(); ++oIt)
     896             :             {
     897           6 :                 OGRDXFFeature *poAttribFeature = oIt->first->CloneDXFFeature();
     898             : 
     899           6 :                 poAttribFeature->SetField("Text", oIt->second);
     900             : 
     901             :                 // Replace text in the style string
     902           6 :                 const char *poStyleString = poAttribFeature->GetStyleString();
     903           6 :                 if (poStyleString && STARTS_WITH(poStyleString, "LABEL("))
     904             :                 {
     905          12 :                     CPLString osNewStyle = poStyleString;
     906           6 :                     const size_t nTextStartPos = osNewStyle.find(",t:\"");
     907           6 :                     if (nTextStartPos != std::string::npos)
     908             :                     {
     909           6 :                         size_t nTextEndPos = nTextStartPos + 4;
     910          12 :                         while (nTextEndPos < osNewStyle.size() &&
     911           6 :                                osNewStyle[nTextEndPos] != '\"')
     912             :                         {
     913           0 :                             nTextEndPos++;
     914           0 :                             if (osNewStyle[nTextEndPos] == '\\')
     915           0 :                                 nTextEndPos++;
     916             :                         }
     917             : 
     918           6 :                         if (nTextEndPos < osNewStyle.size())
     919             :                         {
     920             :                             osNewStyle.replace(
     921             :                                 nTextStartPos + 4,
     922           6 :                                 nTextEndPos - (nTextStartPos + 4), oIt->second);
     923           6 :                             poAttribFeature->SetStyleString(osNewStyle);
     924             :                         }
     925             :                     }
     926             :                 }
     927             : 
     928             :                 // The following bits are copied from
     929             :                 // OGRDXFLayer::InsertBlockInline
     930           6 :                 if (poAttribFeature->GetGeometryRef())
     931             :                 {
     932           6 :                     poAttribFeature->GetGeometryRef()->transform(
     933           6 :                         &oBlockTransformer);
     934             :                 }
     935             : 
     936          12 :                 if (EQUAL(poAttribFeature->GetFieldAsString("Layer"), "0") &&
     937           6 :                     !EQUAL(poOverallFeature->GetFieldAsString("Layer"), ""))
     938             :                 {
     939           6 :                     poAttribFeature->SetField(
     940             :                         "Layer", poOverallFeature->GetFieldAsString("Layer"));
     941             :                 }
     942             : 
     943           6 :                 PrepareFeatureStyle(poAttribFeature, poOverallFeature);
     944             : 
     945           6 :                 ACAdjustText(oBlockTransformer.dfAngle * 180 / M_PI,
     946             :                              oBlockTransformer.dfXScale,
     947             :                              oBlockTransformer.dfYScale, poAttribFeature);
     948             : 
     949           6 :                 if (!EQUAL(poOverallFeature->GetFieldAsString("EntityHandle"),
     950             :                            ""))
     951             :                 {
     952           6 :                     poAttribFeature->SetField(
     953             :                         "EntityHandle",
     954             :                         poOverallFeature->GetFieldAsString("EntityHandle"));
     955             :                 }
     956             : 
     957           6 :                 apoPendingFeatures.push(poAttribFeature);
     958             :             }
     959             :         }
     960             :     }
     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          18 :     if (osText.empty() || osText == " ")
     969             :     {
     970           8 :         delete poOverallFeature;
     971           8 :         return poLeaderFeature;
     972             :     }
     973             : 
     974          10 :     OGRDXFFeature *poLabelFeature = poOverallFeature->CloneDXFFeature();
     975             : 
     976          10 :     poLabelFeature->SetField("Text", osText);
     977          10 :     poLabelFeature->SetGeometryDirectly(new OGRPoint(dfTextX, dfTextY));
     978             : 
     979          20 :     CPLString osStyle;
     980             :     char szBuffer[64];
     981             : 
     982             :     const CPLString osStyleName =
     983          10 :         poDS->GetTextStyleNameByHandle(osTextStyleHandle);
     984             : 
     985             :     // Font name
     986          10 :     osStyle.Printf("LABEL(f:\"");
     987             : 
     988             :     // Preserve legacy behavior of specifying "Arial" as a default font name.
     989          10 :     osStyle += poDS->LookupTextStyleProperty(osStyleName, "Font", "Arial");
     990             : 
     991          10 :     osStyle += "\"";
     992             : 
     993             :     // Bold, italic
     994          10 :     if (EQUAL(poDS->LookupTextStyleProperty(osStyleName, "Bold", "0"), "1"))
     995             :     {
     996           0 :         osStyle += ",bo:1";
     997             :     }
     998          10 :     if (EQUAL(poDS->LookupTextStyleProperty(osStyleName, "Italic", "0"), "1"))
     999             :     {
    1000           2 :         osStyle += ",it:1";
    1001             :     }
    1002             : 
    1003             :     osStyle +=
    1004          20 :         CPLString().Printf(",t:\"%s\",p:%d", osText.c_str(),
    1005          10 :                            nTextAlignment + 6);  // 7,8,9: vertical align top
    1006             : 
    1007          10 :     if (dfTextAngle != 0.0)
    1008             :     {
    1009           2 :         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextAngle);
    1010           2 :         osStyle += CPLString().Printf(",a:%s", szBuffer);
    1011             :     }
    1012             : 
    1013          10 :     if (dfTextHeight != 0.0)
    1014             :     {
    1015          10 :         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.3g", dfTextHeight);
    1016          10 :         osStyle += CPLString().Printf(",s:%sg", szBuffer);
    1017             :     }
    1018             : 
    1019             :     const char *pszWidthFactor =
    1020          10 :         poDS->LookupTextStyleProperty(osStyleName, "Width", "1");
    1021          10 :     if (pszWidthFactor && CPLAtof(pszWidthFactor) != 1.0)
    1022             :     {
    1023           2 :         CPLsnprintf(szBuffer, sizeof(szBuffer), "%.4g",
    1024           2 :                     CPLAtof(pszWidthFactor) * 100.0);
    1025           2 :         osStyle += CPLString().Printf(",w:%s", szBuffer);
    1026             :     }
    1027             : 
    1028             :     // Color
    1029          10 :     osStyle += ",c:";
    1030          10 :     osStyle += poLabelFeature->GetColor(poDS);
    1031             : 
    1032          10 :     osStyle += ")";
    1033             : 
    1034          10 :     poLabelFeature->SetStyleString(osStyle);
    1035             : 
    1036          10 :     apoPendingFeatures.push(poLabelFeature);
    1037             : 
    1038          10 :     delete poOverallFeature;
    1039          10 :     return poLeaderFeature;
    1040             : }
    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          42 : static void GenerateDefaultArrowhead(OGRDXFFeature *const poArrowheadFeature,
    1050             :                                      const OGRPoint &oPoint1,
    1051             :                                      const OGRPoint &oPoint2,
    1052             :                                      const double dfArrowheadScale)
    1053             : 
    1054             : {
    1055             :     // calculate the baseline to be expanded out into arrowheads
    1056             :     const double dfParallelPartX =
    1057          42 :         dfArrowheadScale * (oPoint2.getX() - oPoint1.getX());
    1058             :     const double dfParallelPartY =
    1059          42 :         dfArrowheadScale * (oPoint2.getY() - oPoint1.getY());
    1060             :     // ...and drop a perpendicular
    1061          42 :     const double dfPerpPartX = dfParallelPartY;
    1062          42 :     const double dfPerpPartY = -dfParallelPartX;
    1063             : 
    1064          42 :     OGRLinearRing *poLinearRing = new OGRLinearRing();
    1065          42 :     poLinearRing->setPoint(
    1066          42 :         0, oPoint1.getX() + dfParallelPartX + dfPerpPartX / 6,
    1067          42 :         oPoint1.getY() + dfParallelPartY + dfPerpPartY / 6, oPoint1.getZ());
    1068          42 :     poLinearRing->setPoint(1, oPoint1.getX(), oPoint1.getY(), oPoint1.getZ());
    1069          42 :     poLinearRing->setPoint(
    1070          42 :         2, oPoint1.getX() + dfParallelPartX - dfPerpPartX / 6,
    1071          42 :         oPoint1.getY() + dfParallelPartY - dfPerpPartY / 6, oPoint1.getZ());
    1072          42 :     poLinearRing->closeRings();
    1073             : 
    1074          42 :     OGRPolygon *poPoly = new OGRPolygon();
    1075          42 :     poPoly->addRingDirectly(poLinearRing);
    1076             : 
    1077          42 :     poArrowheadFeature->SetGeometryDirectly(poPoly);
    1078          42 : }
    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          64 : void OGRDXFLayer::InsertArrowhead(OGRDXFFeature *const poFeature,
    1090             :                                   const CPLString &osBlockHandle,
    1091             :                                   OGRLineString *const poLine,
    1092             :                                   const double dfArrowheadSize,
    1093             :                                   const bool bReverse /* = false */)
    1094             : {
    1095          64 :     OGRPoint oPoint1, oPoint2;
    1096          64 :     poLine->getPoint(bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1);
    1097          64 :     poLine->getPoint(bReverse ? poLine->getNumPoints() - 2 : 1, &oPoint2);
    1098             : 
    1099          64 :     const double dfFirstSegmentLength = PointDist(
    1100             :         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          64 :     if (dfArrowheadSize == 0.0 || dfFirstSegmentLength == 0.0 ||
    1105          62 :         dfArrowheadSize > 0.5 * dfFirstSegmentLength)
    1106             :     {
    1107          12 :         return;
    1108             :     }
    1109             : 
    1110          52 :     OGRDXFFeature *poArrowheadFeature = poFeature->CloneDXFFeature();
    1111             : 
    1112             :     // Convert the block handle to a block name.
    1113         104 :     CPLString osBlockName = "";
    1114             : 
    1115          52 :     if (osBlockHandle != "")
    1116          12 :         osBlockName = poDS->GetBlockNameByRecordHandle(osBlockHandle);
    1117             : 
    1118         104 :     OGRDXFFeatureQueue apoExtraFeatures;
    1119             : 
    1120             :     // If the block doesn't exist, we need to fall back to the
    1121             :     // default arrowhead.
    1122          52 :     if (osBlockName == "")
    1123             :     {
    1124          42 :         GenerateDefaultArrowhead(poArrowheadFeature, oPoint1, oPoint2,
    1125             :                                  dfArrowheadSize / dfFirstSegmentLength);
    1126             : 
    1127          42 :         PrepareBrushStyle(poArrowheadFeature);
    1128             :     }
    1129             :     else
    1130             :     {
    1131             :         // Build a transformer to insert the arrowhead block with the
    1132             :         // required location, angle and scale.
    1133          10 :         OGRDXFInsertTransformer oTransformer;
    1134          10 :         oTransformer.dfXOffset = oPoint1.getX();
    1135          10 :         oTransformer.dfYOffset = oPoint1.getY();
    1136          10 :         oTransformer.dfZOffset = oPoint1.getZ();
    1137             :         // Arrowhead blocks always point to the right (--->)
    1138          10 :         oTransformer.dfAngle = atan2(oPoint2.getY() - oPoint1.getY(),
    1139          10 :                                      oPoint2.getX() - oPoint1.getX()) +
    1140             :                                M_PI;
    1141          10 :         oTransformer.dfXScale = oTransformer.dfYScale = oTransformer.dfZScale =
    1142             :             dfArrowheadSize;
    1143             : 
    1144             :         // Insert the block.
    1145             :         try
    1146             :         {
    1147          16 :             poArrowheadFeature = InsertBlockInline(
    1148          10 :                 CPLGetErrorCounter(), osBlockName, std::move(oTransformer),
    1149             :                 poArrowheadFeature, apoExtraFeatures, true, false);
    1150             :         }
    1151           6 :         catch (const std::invalid_argument &)
    1152             :         {
    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           6 :             delete poArrowheadFeature;
    1158           6 :             poArrowheadFeature = nullptr;
    1159             :         }
    1160             :     }
    1161             : 
    1162             :     // Add the arrowhead geometries to the pending feature stack.
    1163          52 :     if (poArrowheadFeature)
    1164             :     {
    1165          42 :         apoPendingFeatures.push(poArrowheadFeature);
    1166             :     }
    1167          60 :     while (!apoExtraFeatures.empty())
    1168             :     {
    1169           8 :         apoPendingFeatures.push(apoExtraFeatures.front());
    1170           8 :         apoExtraFeatures.pop();
    1171             :     }
    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             :     static const char *apszSpecialArrowheads[] = {
    1178             :         "_ArchTick", "_DotSmall", "_Integral", "_None", "_Oblique", "_Small"};
    1179             : 
    1180          52 :     if (std::find(apszSpecialArrowheads, apszSpecialArrowheads + 6,
    1181          52 :                   osBlockName) == (apszSpecialArrowheads + 6))
    1182             :     {
    1183          46 :         oPoint1.setX(oPoint1.getX() + dfArrowheadSize *
    1184          46 :                                           (oPoint2.getX() - oPoint1.getX()) /
    1185             :                                           dfFirstSegmentLength);
    1186          46 :         oPoint1.setY(oPoint1.getY() + dfArrowheadSize *
    1187          46 :                                           (oPoint2.getY() - oPoint1.getY()) /
    1188             :                                           dfFirstSegmentLength);
    1189             : 
    1190          46 :         poLine->setPoint(bReverse ? poLine->getNumPoints() - 1 : 0, &oPoint1);
    1191             :     }
    1192             : }
    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          14 : inline void setRow(GDALMatrix &m, int row, DXFTriple const &t)
    1211             : {
    1212          14 :     m(row, 0) = t.dfX;
    1213          14 :     m(row, 1) = t.dfY;
    1214          14 :     m(row, 2) = t.dfZ;
    1215          14 : }
    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           6 : 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             : {
    1251           6 :     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           6 :     const int nPoints = static_cast<int>(aoDataPoints.size());
    1257             : 
    1258           6 :     CPLAssert(nPoints > 0);
    1259           6 :     CPLAssert(nPoints == static_cast<int>(adfParameters.size()));
    1260             : 
    1261             :     // RAM consumption is quadratic in the number of control points.
    1262           6 :     if (nPoints >
    1263           6 :         atoi(CPLGetConfigOption("DXF_MAX_BSPLINE_CONTROL_POINTS", "2000")))
    1264             :     {
    1265           3 :         CPLError(CE_Failure, CPLE_AppDefined,
    1266             :                  "Too many control points (%d) for spline leader. "
    1267             :                  "Set DXF_MAX_BSPLINE_CONTROL_POINTS configuration "
    1268             :                  "option to a higher value to remove this limitation "
    1269             :                  "(at the cost of significant RAM consumption)",
    1270             :                  nPoints);
    1271           3 :         return std::vector<DXFTriple>();
    1272             :     }
    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           6 :     GDALMatrix D(nPoints + 2, 3);
    1281             : 
    1282           3 :     setRow(D, 0, aoDataPoints[0]);
    1283           5 :     for (int iIndex = 1; iIndex < nPoints - 1; iIndex++)
    1284           2 :         setRow(D, iIndex + 1, aoDataPoints[iIndex]);
    1285           3 :     setRow(D, nPoints + 1, aoDataPoints[nPoints - 1]);
    1286             : 
    1287           3 :     const double dfStartMultiplier = adfKnots[nDegree + 1] / nDegree;
    1288           3 :     oStartTangent *= dfStartMultiplier;
    1289           3 :     setRow(D, 1, oStartTangent);
    1290             : 
    1291           3 :     const double dfEndMultiplier = (1.0 - adfKnots[nPoints + 1]) / nDegree;
    1292           3 :     oEndTangent *= dfEndMultiplier;
    1293           3 :     setRow(D, nPoints, oEndTangent);
    1294             : 
    1295           6 :     GDALMatrix N(nPoints + 2, nPoints + 2);
    1296             :     // First control point will be the first data point
    1297           3 :     N(0, 0) = 1.0;
    1298             : 
    1299             :     // Start tangent determines the second control point
    1300           3 :     N(1, 0) = -1.0;
    1301           3 :     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           6 :     std::vector<double> adfTempRow(nPoints + 2 + nDegree, 0.0);
    1307           5 :     for (int iRow = 2; iRow < nPoints; iRow++)
    1308             :     {
    1309           2 :         basis(nDegree + 1, adfParameters[iRow - 1], nPoints + 2,
    1310           2 :               const_cast<double *>(&adfKnots[0]) - 1, &adfTempRow[0] - 1);
    1311          12 :         for (int iCol = 0; iCol < nPoints + 2; ++iCol)
    1312          10 :             N(iRow, iCol) = adfTempRow[iCol];
    1313             :     }
    1314             : 
    1315             :     // End tangent determines the second-last control point
    1316           3 :     N(nPoints, nPoints) = -1.0;
    1317           3 :     N(nPoints, nPoints + 1) = 1.0;
    1318             : 
    1319             :     // Last control point will be the last data point
    1320           3 :     N(nPoints + 1, nPoints + 1) = 1.0;
    1321             : 
    1322             :     // Solve the linear system
    1323           6 :     GDALMatrix P(nPoints + 2, 3);
    1324           3 :     GDALLinearSystemSolve(N, D, P);
    1325             : 
    1326           6 :     std::vector<DXFTriple> aoControlPoints(nPoints + 2);
    1327          17 :     for (int iRow = 0; iRow < nPoints + 2; iRow++)
    1328             :     {
    1329          14 :         aoControlPoints[iRow].dfX = P(iRow, 0);
    1330          14 :         aoControlPoints[iRow].dfY = P(iRow, 1);
    1331          14 :         aoControlPoints[iRow].dfZ = P(iRow, 2);
    1332             :     }
    1333             : 
    1334           3 :     return aoControlPoints;
    1335             : }
    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           6 : static void InterpolateSpline(OGRLineString *const poLine,
    1352             :                               const DXFTriple &oEndTangentDirection)
    1353             : {
    1354           6 :     int nDataPoints = static_cast<int>(poLine->getNumPoints());
    1355           6 :     if (nDataPoints < 2)
    1356           3 :         return;
    1357             : 
    1358             :     // Transfer line vertices into DXFTriple objects
    1359           6 :     std::vector<DXFTriple> aoDataPoints;
    1360           6 :     OGRPoint oPrevPoint;
    1361          22 :     for (int iIndex = 0; iIndex < nDataPoints; iIndex++)
    1362             :     {
    1363          16 :         OGRPoint oPoint;
    1364          16 :         poLine->getPoint(iIndex, &oPoint);
    1365             : 
    1366             :         // Remove sequential duplicate points
    1367          16 :         if (iIndex > 0 && oPrevPoint.Equals(&oPoint))
    1368           0 :             continue;
    1369             : 
    1370          16 :         aoDataPoints.push_back(
    1371          16 :             DXFTriple(oPoint.getX(), oPoint.getY(), oPoint.getZ()));
    1372          16 :         oPrevPoint = std::move(oPoint);
    1373             :     }
    1374           6 :     nDataPoints = static_cast<int>(aoDataPoints.size());
    1375           6 :     if (nDataPoints < 2)
    1376           0 :         return;
    1377             : 
    1378             :     // Work out the chord length parameterisation
    1379           6 :     std::vector<double> adfParameters;
    1380           6 :     adfParameters.push_back(0.0);
    1381          16 :     for (int iIndex = 1; iIndex < nDataPoints; iIndex++)
    1382             :     {
    1383             :         const double dfParameter =
    1384          10 :             adfParameters[iIndex - 1] +
    1385          10 :             PointDist(aoDataPoints[iIndex - 1].dfX,
    1386          10 :                       aoDataPoints[iIndex - 1].dfY,
    1387          10 :                       aoDataPoints[iIndex - 1].dfZ, aoDataPoints[iIndex].dfX,
    1388          10 :                       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          10 :         if (dfParameter == adfParameters[iIndex - 1])
    1394           0 :             return;
    1395             : 
    1396          10 :         adfParameters.push_back(dfParameter);
    1397             :     }
    1398             : 
    1399           6 :     const double dfTotalChordLength = adfParameters.back();
    1400             : 
    1401             :     // Start tangent can be worked out from the first chord
    1402           6 :     DXFTriple oStartTangent(aoDataPoints[1].dfX - aoDataPoints[0].dfX,
    1403           6 :                             aoDataPoints[1].dfY - aoDataPoints[0].dfY,
    1404          18 :                             aoDataPoints[1].dfZ - aoDataPoints[0].dfZ);
    1405           6 :     oStartTangent *= dfTotalChordLength / adfParameters[1];
    1406             : 
    1407             :     // If end tangent is zero, it is worked out from the last chord
    1408           6 :     DXFTriple oEndTangent = oEndTangentDirection;
    1409           6 :     if (oEndTangent.dfX == 0.0 && oEndTangent.dfY == 0.0 &&
    1410           2 :         oEndTangent.dfZ == 0.0)
    1411             :     {
    1412          10 :         oEndTangent = DXFTriple(aoDataPoints[nDataPoints - 1].dfX -
    1413           2 :                                     aoDataPoints[nDataPoints - 2].dfX,
    1414           4 :                                 aoDataPoints[nDataPoints - 1].dfY -
    1415           2 :                                     aoDataPoints[nDataPoints - 2].dfY,
    1416           2 :                                 aoDataPoints[nDataPoints - 1].dfZ -
    1417           2 :                                     aoDataPoints[nDataPoints - 2].dfZ);
    1418           2 :         oEndTangent /= dfTotalChordLength - adfParameters[nDataPoints - 2];
    1419             :     }
    1420             : 
    1421             :     // End tangent direction is multiplied by total chord length
    1422           6 :     oEndTangent *= dfTotalChordLength;
    1423             : 
    1424             :     // Normalise the parameter vector
    1425          16 :     for (int iIndex = 1; iIndex < nDataPoints; iIndex++)
    1426          10 :         adfParameters[iIndex] /= dfTotalChordLength;
    1427             : 
    1428             :     // Generate a knot vector
    1429           6 :     const int nDegree = 3;
    1430           6 :     std::vector<double> adfKnots(aoDataPoints.size() + nDegree + 3, 0.0);
    1431             :     std::copy(adfParameters.begin(), adfParameters.end(),
    1432           6 :               adfKnots.begin() + nDegree);
    1433           6 :     std::fill(adfKnots.end() - nDegree, adfKnots.end(), 1.0);
    1434             : 
    1435             :     // Calculate the spline control points
    1436             :     std::vector<DXFTriple> aoControlPoints =
    1437             :         GetBSplineControlPoints(adfParameters, adfKnots, aoDataPoints, nDegree,
    1438           6 :                                 oStartTangent, oEndTangent);
    1439           6 :     const int nControlPoints = static_cast<int>(aoControlPoints.size());
    1440             : 
    1441           6 :     if (nControlPoints == 0)
    1442           3 :         return;
    1443             : 
    1444             :     // Interpolate the spline using the intronurbs code
    1445           3 :     int nWantedPoints = nControlPoints * 8;
    1446           6 :     std::vector<double> adfWeights(nControlPoints, 1.0);
    1447           6 :     std::vector<double> adfPoints(3 * nWantedPoints, 0.0);
    1448             : 
    1449           3 :     rbspline2(nControlPoints, nDegree + 1, nWantedPoints,
    1450           3 :               reinterpret_cast<double *>(&aoControlPoints[0]) - 1,
    1451           3 :               &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           3 :     const int bIs3D = poLine->Is3D();
    1455           3 :     poLine->empty();
    1456         115 :     for (int iIndex = 0; iIndex < nWantedPoints; iIndex++)
    1457             :     {
    1458         112 :         poLine->addPoint(adfPoints[iIndex * 3], adfPoints[iIndex * 3 + 1],
    1459         112 :                          adfPoints[iIndex * 3 + 2]);
    1460             :     }
    1461           3 :     if (!bIs3D)
    1462           2 :         poLine->flattenTo2D();
    1463             : }

Generated by: LCOV version 1.14