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

Generated by: LCOV version 1.14