LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/dxf - ogrdxfwriterlayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 575 649 88.6 %
Date: 2025-08-01 10:10:57 Functions: 23 23 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  DXF Translator
       4             :  * Purpose:  Implements OGRDXFWriterLayer - the OGRLayer class used for
       5             :  *           writing a DXF file.
       6             :  * Author:   Frank Warmerdam, warmerdam@pobox.com
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2009, Frank Warmerdam <warmerdam@pobox.com>
      10             :  * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
      11             :  *
      12             :  * SPDX-License-Identifier: MIT
      13             :  ****************************************************************************/
      14             : 
      15             : #include "ogr_dxf.h"
      16             : #include "cpl_conv.h"
      17             : #include "cpl_string.h"
      18             : #include "ogr_featurestyle.h"
      19             : 
      20             : #include <cstdlib>
      21             : 
      22             : /************************************************************************/
      23             : /*                         OGRDXFWriterLayer()                          */
      24             : /************************************************************************/
      25             : 
      26          59 : OGRDXFWriterLayer::OGRDXFWriterLayer(OGRDXFWriterDS *poDSIn, VSILFILE *fpIn)
      27             :     : fp(fpIn),
      28             :       poFeatureDefn(nullptr),  // TODO(schwehr): Can I move the new here?
      29          59 :       poDS(poDSIn)
      30             : {
      31          59 :     nNextAutoID = 1;
      32          59 :     bWriteHatch = CPLTestBool(CPLGetConfigOption("DXF_WRITE_HATCH", "YES"));
      33             : 
      34          59 :     poFeatureDefn = new OGRFeatureDefn("entities");
      35          59 :     poFeatureDefn->Reference();
      36             : 
      37          59 :     OGRDXFDataSource::AddStandardFields(poFeatureDefn, ODFM_IncludeBlockFields);
      38          59 : }
      39             : 
      40             : /************************************************************************/
      41             : /*                         ~OGRDXFWriterLayer()                         */
      42             : /************************************************************************/
      43             : 
      44         118 : OGRDXFWriterLayer::~OGRDXFWriterLayer()
      45             : 
      46             : {
      47          59 :     if (poFeatureDefn)
      48          59 :         poFeatureDefn->Release();
      49         118 : }
      50             : 
      51             : /************************************************************************/
      52             : /*                              ResetFP()                               */
      53             : /*                                                                      */
      54             : /*      Redirect output.  Mostly used for writing block definitions.    */
      55             : /************************************************************************/
      56             : 
      57           2 : void OGRDXFWriterLayer::ResetFP(VSILFILE *fpNew)
      58             : 
      59             : {
      60           2 :     fp = fpNew;
      61           2 : }
      62             : 
      63             : /************************************************************************/
      64             : /*                           TestCapability()                           */
      65             : /************************************************************************/
      66             : 
      67         109 : int OGRDXFWriterLayer::TestCapability(const char *pszCap)
      68             : 
      69             : {
      70         109 :     if (EQUAL(pszCap, OLCStringsAsUTF8))
      71           0 :         return TRUE;
      72         109 :     else if (EQUAL(pszCap, OLCSequentialWrite))
      73          16 :         return TRUE;
      74             :     else
      75          93 :         return FALSE;
      76             : }
      77             : 
      78             : /************************************************************************/
      79             : /*                            CreateField()                             */
      80             : /*                                                                      */
      81             : /*      This is really a dummy as our fields are precreated.            */
      82             : /************************************************************************/
      83             : 
      84          80 : OGRErr OGRDXFWriterLayer::CreateField(const OGRFieldDefn *poField,
      85             :                                       int bApproxOK)
      86             : 
      87             : {
      88          80 :     if (poFeatureDefn->GetFieldIndex(poField->GetNameRef()) >= 0 && bApproxOK)
      89           0 :         return OGRERR_NONE;
      90          80 :     if (EQUAL(poField->GetNameRef(), "OGR_STYLE"))
      91             :     {
      92           0 :         poFeatureDefn->AddFieldDefn(poField);
      93           0 :         return OGRERR_NONE;
      94             :     }
      95             : 
      96          80 :     CPLError(CE_Failure, CPLE_AppDefined,
      97             :              "DXF layer does not support arbitrary field creation, field '%s' "
      98             :              "not created.",
      99             :              poField->GetNameRef());
     100             : 
     101          80 :     return OGRERR_FAILURE;
     102             : }
     103             : 
     104             : /************************************************************************/
     105             : /*                             WriteValue()                             */
     106             : /************************************************************************/
     107             : 
     108         784 : int OGRDXFWriterLayer::WriteValue(int nCode, const char *pszValue)
     109             : 
     110             : {
     111         784 :     CPLString osLinePair;
     112             : 
     113         784 :     osLinePair.Printf("%3d\n", nCode);
     114             : 
     115         784 :     if (strlen(pszValue) < 255)
     116         784 :         osLinePair += pszValue;
     117             :     else
     118           0 :         osLinePair.append(pszValue, 255);
     119             : 
     120         784 :     osLinePair += "\n";
     121             : 
     122         784 :     return VSIFWriteL(osLinePair.c_str(), 1, osLinePair.size(), fp) ==
     123        1568 :            osLinePair.size();
     124             : }
     125             : 
     126             : /************************************************************************/
     127             : /*                             WriteValue()                             */
     128             : /************************************************************************/
     129             : 
     130         548 : int OGRDXFWriterLayer::WriteValue(int nCode, int nValue)
     131             : 
     132             : {
     133         548 :     CPLString osLinePair;
     134             : 
     135         548 :     osLinePair.Printf("%3d\n%d\n", nCode, nValue);
     136             : 
     137         548 :     return VSIFWriteL(osLinePair.c_str(), 1, osLinePair.size(), fp) ==
     138        1096 :            osLinePair.size();
     139             : }
     140             : 
     141             : /************************************************************************/
     142             : /*                             WriteValue()                             */
     143             : /************************************************************************/
     144             : 
     145         800 : int OGRDXFWriterLayer::WriteValue(int nCode, double dfValue)
     146             : 
     147             : {
     148             :     char szLinePair[64];
     149             : 
     150         800 :     CPLsnprintf(szLinePair, sizeof(szLinePair), "%3d\n%.15g\n", nCode, dfValue);
     151         800 :     size_t nLen = strlen(szLinePair);
     152             : 
     153         800 :     return VSIFWriteL(szLinePair, 1, nLen, fp) == nLen;
     154             : }
     155             : 
     156             : /************************************************************************/
     157             : /*                             WriteCore()                              */
     158             : /*                                                                      */
     159             : /*      Write core fields common to all sorts of elements.              */
     160             : /************************************************************************/
     161             : 
     162         183 : OGRErr OGRDXFWriterLayer::WriteCore(OGRFeature *poFeature,
     163             :                                     const CorePropertiesType &oCoreProperties)
     164             : 
     165             : {
     166             :     /* -------------------------------------------------------------------- */
     167             :     /*      Write out an entity id.  I'm not sure why this is critical,     */
     168             :     /*      but it seems that VoloView will just quietly fail to open       */
     169             :     /*      dxf files without entity ids set on most/all entities.          */
     170             :     /*      Also, for reasons I don't understand these ids seem to have     */
     171             :     /*      to start somewhere around 0x50 hex (80 decimal).                */
     172             :     /* -------------------------------------------------------------------- */
     173         183 :     unsigned int nGotFID = 0;
     174         183 :     poDS->WriteEntityID(fp, nGotFID, poFeature->GetFID());
     175         183 :     poFeature->SetFID(nGotFID);
     176             : 
     177         183 :     WriteValue(100, "AcDbEntity");
     178             : 
     179             :     /* -------------------------------------------------------------------- */
     180             :     /*      For now we assign everything to the default layer - layer       */
     181             :     /*      "0" - if there is no layer property on the source features.     */
     182             :     /* -------------------------------------------------------------------- */
     183         183 :     const char *pszLayer = poFeature->GetFieldAsString("Layer");
     184         183 :     if (pszLayer == nullptr || strlen(pszLayer) == 0)
     185             :     {
     186         175 :         WriteValue(8, "0");
     187             :     }
     188             :     else
     189             :     {
     190          16 :         CPLString osSanitizedLayer(pszLayer);
     191             :         // Replaced restricted characters with underscore
     192             :         // See
     193             :         // http://docs.autodesk.com/ACD/2010/ENU/AutoCAD%202010%20User%20Documentation/index.html?url=WS1a9193826455f5ffa23ce210c4a30acaf-7345.htm,topicNumber=d0e41665
     194           8 :         const char achForbiddenChars[] = {'<', '>', '/', '\\', '"', ':',
     195             :                                           ';', '?', '*', '|',  '=', '\''};
     196         104 :         for (size_t i = 0; i < CPL_ARRAYSIZE(achForbiddenChars); ++i)
     197             :         {
     198          96 :             osSanitizedLayer.replaceAll(achForbiddenChars[i], '_');
     199             :         }
     200             : 
     201             :         // also remove newline characters (#15067)
     202           8 :         osSanitizedLayer.replaceAll("\r\n", "_");
     203           8 :         osSanitizedLayer.replaceAll('\r', '_');
     204           8 :         osSanitizedLayer.replaceAll('\n', '_');
     205             : 
     206             :         auto osExists =
     207          16 :             poDS->oHeaderDS.LookupLayerProperty(osSanitizedLayer, "Exists");
     208          15 :         if (!osExists &&
     209           7 :             CSLFindString(poDS->papszLayersToCreate, osSanitizedLayer) == -1)
     210             :         {
     211           6 :             poDS->papszLayersToCreate =
     212           3 :                 CSLAddString(poDS->papszLayersToCreate, osSanitizedLayer);
     213             :         }
     214             : 
     215           8 :         WriteValue(8, osSanitizedLayer);
     216             :     }
     217             : 
     218         200 :     for (const auto &oProp : oCoreProperties)
     219             :     {
     220          17 :         if (oProp.first == PROP_RGBA_COLOR)
     221             :         {
     222          17 :             bool bPerfectMatch = false;
     223          17 :             const char *pszColor = oProp.second.c_str();
     224          17 :             const int nColor = ColorStringToDXFColor(pszColor, bPerfectMatch);
     225          17 :             if (nColor >= 0)
     226             :             {
     227          17 :                 WriteValue(62, nColor);
     228             : 
     229          17 :                 unsigned int nRed = 0;
     230          17 :                 unsigned int nGreen = 0;
     231          17 :                 unsigned int nBlue = 0;
     232          17 :                 unsigned int nOpacity = 255;
     233             : 
     234          17 :                 const int nCount = sscanf(pszColor, "#%2x%2x%2x%2x", &nRed,
     235             :                                           &nGreen, &nBlue, &nOpacity);
     236          17 :                 if (nCount >= 3 && !bPerfectMatch)
     237             :                 {
     238           8 :                     WriteValue(420, static_cast<int>(nBlue | (nGreen << 8) |
     239           8 :                                                      (nRed << 16)));
     240             :                 }
     241          17 :                 if (nCount == 4)
     242             :                 {
     243           2 :                     WriteValue(440, static_cast<int>(nOpacity | (2 << 24)));
     244             :                 }
     245             :             }
     246             :         }
     247             :         else
     248             :         {
     249             :             // If this happens, this is a coding error
     250           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     251           0 :                      "BUG! Unhandled core property %d", oProp.first);
     252             :         }
     253             :     }
     254             : 
     255         183 :     return OGRERR_NONE;
     256             : }
     257             : 
     258             : /************************************************************************/
     259             : /*                            WriteINSERT()                             */
     260             : /************************************************************************/
     261             : 
     262           7 : OGRErr OGRDXFWriterLayer::WriteINSERT(OGRFeature *poFeature)
     263             : 
     264             : {
     265          14 :     CorePropertiesType oCoreProperties;
     266             : 
     267             :     // Write style symbol color
     268           7 :     OGRStyleTool *poTool = nullptr;
     269          14 :     OGRStyleMgr oSM;
     270           7 :     if (poFeature->GetStyleString() != nullptr)
     271             :     {
     272           0 :         oSM.InitFromFeature(poFeature);
     273             : 
     274           0 :         if (oSM.GetPartCount() > 0)
     275           0 :             poTool = oSM.GetPart(0);
     276             :     }
     277           7 :     if (poTool && poTool->GetType() == OGRSTCSymbol)
     278             :     {
     279           0 :         OGRStyleSymbol *poSymbol = cpl::down_cast<OGRStyleSymbol *>(poTool);
     280             :         GBool bDefault;
     281           0 :         const char *pszColor = poSymbol->Color(bDefault);
     282           0 :         if (pszColor && !bDefault)
     283           0 :             oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
     284             :     }
     285           7 :     delete poTool;
     286             : 
     287           7 :     WriteValue(0, "INSERT");
     288           7 :     WriteCore(poFeature, oCoreProperties);
     289           7 :     WriteValue(100, "AcDbBlockReference");
     290           7 :     WriteValue(2, poFeature->GetFieldAsString("BlockName"));
     291             : 
     292             :     /* -------------------------------------------------------------------- */
     293             :     /*      Write location in OCS.                                          */
     294             :     /* -------------------------------------------------------------------- */
     295           7 :     int nCoordCount = 0;
     296             :     const double *padfCoords =
     297           7 :         poFeature->GetFieldAsDoubleList("BlockOCSCoords", &nCoordCount);
     298             : 
     299           7 :     if (nCoordCount == 3)
     300             :     {
     301           0 :         WriteValue(10, padfCoords[0]);
     302           0 :         WriteValue(20, padfCoords[1]);
     303           0 :         if (!WriteValue(30, padfCoords[2]))
     304           0 :             return OGRERR_FAILURE;
     305             :     }
     306             :     else
     307             :     {
     308             :         // We don't have an OCS; we will just assume that the location of
     309             :         // the geometry (in WCS) is the correct insertion point.
     310           7 :         OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
     311             : 
     312           7 :         WriteValue(10, poPoint->getX());
     313           7 :         if (!WriteValue(20, poPoint->getY()))
     314           0 :             return OGRERR_FAILURE;
     315             : 
     316           7 :         if (poPoint->getGeometryType() == wkbPoint25D)
     317             :         {
     318           0 :             if (!WriteValue(30, poPoint->getZ()))
     319           0 :                 return OGRERR_FAILURE;
     320             :         }
     321             :     }
     322             : 
     323             :     /* -------------------------------------------------------------------- */
     324             :     /*      Write scaling.                                                  */
     325             :     /* -------------------------------------------------------------------- */
     326           7 :     int nScaleCount = 0;
     327             :     const double *padfScale =
     328           7 :         poFeature->GetFieldAsDoubleList("BlockScale", &nScaleCount);
     329             : 
     330           7 :     if (nScaleCount == 3)
     331             :     {
     332           1 :         WriteValue(41, padfScale[0]);
     333           1 :         WriteValue(42, padfScale[1]);
     334           1 :         WriteValue(43, padfScale[2]);
     335             :     }
     336             : 
     337             :     /* -------------------------------------------------------------------- */
     338             :     /*      Write rotation.                                                 */
     339             :     /* -------------------------------------------------------------------- */
     340           7 :     const double dfAngle = poFeature->GetFieldAsDouble("BlockAngle");
     341             : 
     342           7 :     if (dfAngle != 0.0)
     343             :     {
     344           1 :         WriteValue(50, dfAngle);  // degrees
     345             :     }
     346             : 
     347             :     /* -------------------------------------------------------------------- */
     348             :     /*      Write OCS normal vector.                                        */
     349             :     /* -------------------------------------------------------------------- */
     350           7 :     int nOCSCount = 0;
     351             :     const double *padfOCS =
     352           7 :         poFeature->GetFieldAsDoubleList("BlockOCSNormal", &nOCSCount);
     353             : 
     354           7 :     if (nOCSCount == 3)
     355             :     {
     356           0 :         WriteValue(210, padfOCS[0]);
     357           0 :         WriteValue(220, padfOCS[1]);
     358           0 :         WriteValue(230, padfOCS[2]);
     359             :     }
     360             : 
     361           7 :     return OGRERR_NONE;
     362             : }
     363             : 
     364             : /************************************************************************/
     365             : /*                             WritePOINT()                             */
     366             : /************************************************************************/
     367             : 
     368         117 : OGRErr OGRDXFWriterLayer::WritePOINT(OGRFeature *poFeature)
     369             : 
     370             : {
     371         234 :     CorePropertiesType oCoreProperties;
     372             : 
     373             :     // Write style pen color
     374         117 :     OGRStyleTool *poTool = nullptr;
     375         234 :     OGRStyleMgr oSM;
     376         117 :     if (poFeature->GetStyleString() != nullptr)
     377             :     {
     378           0 :         oSM.InitFromFeature(poFeature);
     379             : 
     380           0 :         if (oSM.GetPartCount() > 0)
     381           0 :             poTool = oSM.GetPart(0);
     382             :     }
     383         117 :     if (poTool && poTool->GetType() == OGRSTCPen)
     384             :     {
     385           0 :         OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
     386             :         GBool bDefault;
     387           0 :         const char *pszColor = poPen->Color(bDefault);
     388           0 :         if (pszColor && !bDefault)
     389           0 :             oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
     390             :     }
     391         117 :     delete poTool;
     392             : 
     393         117 :     WriteValue(0, "POINT");
     394         117 :     WriteCore(poFeature, oCoreProperties);
     395         117 :     WriteValue(100, "AcDbPoint");
     396             : 
     397         117 :     OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
     398             : 
     399         117 :     WriteValue(10, poPoint->getX());
     400         117 :     if (!WriteValue(20, poPoint->getY()))
     401           0 :         return OGRERR_FAILURE;
     402             : 
     403         117 :     if (poPoint->getGeometryType() == wkbPoint25D)
     404             :     {
     405           6 :         if (!WriteValue(30, poPoint->getZ()))
     406           0 :             return OGRERR_FAILURE;
     407             :     }
     408             : 
     409         117 :     return OGRERR_NONE;
     410             : }
     411             : 
     412             : /************************************************************************/
     413             : /*                             TextEscape()                             */
     414             : /*                                                                      */
     415             : /*      Translate UTF8 to Win1252 and escape special characters like    */
     416             : /*      newline and space with DXF style escapes.  Note that            */
     417             : /*      non-win1252 unicode characters are translated using the         */
     418             : /*      unicode escape sequence.                                        */
     419             : /************************************************************************/
     420             : 
     421           1 : CPLString OGRDXFWriterLayer::TextEscape(const char *pszInput)
     422             : 
     423             : {
     424           1 :     CPLString osResult;
     425           1 :     wchar_t *panInput = CPLRecodeToWChar(pszInput, CPL_ENC_UTF8, CPL_ENC_UCS2);
     426          31 :     for (int i = 0; panInput[i] != 0; i++)
     427             :     {
     428          30 :         if (panInput[i] == '\n')
     429             :         {
     430           0 :             osResult += "\\P";
     431             :         }
     432          30 :         else if (panInput[i] == ' ')
     433             :         {
     434           2 :             osResult += "\\~";
     435             :         }
     436          28 :         else if (panInput[i] == '\\')
     437             :         {
     438           0 :             osResult += "\\\\";
     439             :         }
     440          28 :         else if (panInput[i] == '^')
     441             :         {
     442           1 :             osResult += "^ ";
     443             :         }
     444          27 :         else if (panInput[i] < ' ')
     445             :         {
     446           1 :             osResult += '^';
     447           1 :             osResult += static_cast<char>(panInput[i] + '@');
     448             :         }
     449          26 :         else if (panInput[i] > 255)
     450             :         {
     451           0 :             CPLString osUnicode;
     452           0 :             osUnicode.Printf("\\U+%04x", (int)panInput[i]);
     453           0 :             osResult += osUnicode;
     454             :         }
     455             :         else
     456             :         {
     457          26 :             osResult += (char)panInput[i];
     458             :         }
     459             :     }
     460             : 
     461           1 :     CPLFree(panInput);
     462             : 
     463           1 :     return osResult;
     464             : }
     465             : 
     466             : /************************************************************************/
     467             : /*                     PrepareTextStyleDefinition()                     */
     468             : /************************************************************************/
     469             : std::map<CPLString, CPLString>
     470           1 : OGRDXFWriterLayer::PrepareTextStyleDefinition(OGRStyleLabel *poLabelTool)
     471             : {
     472             :     GBool bDefault;
     473             : 
     474           1 :     std::map<CPLString, CPLString> oTextStyleDef;
     475             : 
     476             :     /* -------------------------------------------------------------------- */
     477             :     /*      Fetch the data for this text style.                             */
     478             :     /* -------------------------------------------------------------------- */
     479           1 :     const char *pszFontName = poLabelTool->FontName(bDefault);
     480           1 :     if (!bDefault)
     481           1 :         oTextStyleDef["Font"] = pszFontName;
     482             : 
     483           1 :     const GBool bBold = poLabelTool->Bold(bDefault);
     484           1 :     if (!bDefault)
     485           1 :         oTextStyleDef["Bold"] = bBold ? "1" : "0";
     486             : 
     487           1 :     const GBool bItalic = poLabelTool->Italic(bDefault);
     488           1 :     if (!bDefault)
     489           0 :         oTextStyleDef["Italic"] = bItalic ? "1" : "0";
     490             : 
     491           1 :     const double dfStretch = poLabelTool->Stretch(bDefault);
     492           1 :     if (!bDefault)
     493             :     {
     494           1 :         oTextStyleDef["Width"] = CPLString().Printf("%f", dfStretch / 100.0);
     495             :     }
     496             : 
     497           2 :     return oTextStyleDef;
     498             : }
     499             : 
     500             : /************************************************************************/
     501             : /*                             WriteTEXT()                              */
     502             : /************************************************************************/
     503             : 
     504           1 : OGRErr OGRDXFWriterLayer::WriteTEXT(OGRFeature *poFeature)
     505             : 
     506             : {
     507           2 :     CorePropertiesType oCoreProperties;
     508             : 
     509             :     /* -------------------------------------------------------------------- */
     510             :     /*      Do we have styling information?                                 */
     511             :     /* -------------------------------------------------------------------- */
     512           1 :     OGRStyleTool *poTool = nullptr;
     513           2 :     OGRStyleMgr oSM;
     514             : 
     515           1 :     if (poFeature->GetStyleString() != nullptr)
     516             :     {
     517           1 :         oSM.InitFromFeature(poFeature);
     518             : 
     519           1 :         if (oSM.GetPartCount() > 0)
     520           1 :             poTool = oSM.GetPart(0);
     521             :     }
     522             : 
     523           1 :     if (poTool && poTool->GetType() == OGRSTCLabel)
     524             :     {
     525           1 :         OGRStyleLabel *poLabel = cpl::down_cast<OGRStyleLabel *>(poTool);
     526             :         GBool bDefault;
     527           1 :         const char *pszColor = poLabel->ForeColor(bDefault);
     528           1 :         if (pszColor && !bDefault)
     529           1 :             oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
     530             :     }
     531             : 
     532           1 :     WriteValue(0, "MTEXT");
     533           1 :     WriteCore(poFeature, oCoreProperties);
     534           1 :     WriteValue(100, "AcDbMText");
     535             : 
     536             :     /* ==================================================================== */
     537             :     /*      Process the LABEL tool.                                         */
     538             :     /* ==================================================================== */
     539           1 :     double dfDx = 0.0;
     540           1 :     double dfDy = 0.0;
     541             : 
     542           1 :     if (poTool && poTool->GetType() == OGRSTCLabel)
     543             :     {
     544           1 :         OGRStyleLabel *poLabel = cpl::down_cast<OGRStyleLabel *>(poTool);
     545             :         GBool bDefault;
     546             : 
     547             :         /* --------------------------------------------------------------------
     548             :          */
     549             :         /*      Angle */
     550             :         /* --------------------------------------------------------------------
     551             :          */
     552           1 :         const double dfAngle = poLabel->Angle(bDefault);
     553             : 
     554           1 :         if (!bDefault)
     555           1 :             WriteValue(50, dfAngle);
     556             : 
     557             :         /* --------------------------------------------------------------------
     558             :          */
     559             :         /*      Height - We need to fetch this in georeferenced units - I'm */
     560             :         /*      doubt the default translation mechanism will be much good. */
     561             :         /* --------------------------------------------------------------------
     562             :          */
     563           1 :         poTool->SetUnit(OGRSTUGround);
     564           1 :         const double dfHeight = poLabel->Size(bDefault);
     565             : 
     566           1 :         if (!bDefault)
     567           1 :             WriteValue(40, dfHeight);
     568             : 
     569             :         /* --------------------------------------------------------------------
     570             :          */
     571             :         /*      Anchor / Attachment Point */
     572             :         /* --------------------------------------------------------------------
     573             :          */
     574           1 :         const int nAnchor = poLabel->Anchor(bDefault);
     575             : 
     576           1 :         if (!bDefault)
     577             :         {
     578             :             const static int anAnchorMap[] = {-1, 7, 8, 9, 4, 5, 6,
     579             :                                               1,  2, 3, 7, 8, 9};
     580             : 
     581           0 :             if (nAnchor > 0 && nAnchor < 13)
     582           0 :                 WriteValue(71, anAnchorMap[nAnchor]);
     583             :         }
     584             : 
     585             :         /* --------------------------------------------------------------------
     586             :          */
     587             :         /*      Offset */
     588             :         /* --------------------------------------------------------------------
     589             :          */
     590           1 :         dfDx = poLabel->SpacingX(bDefault);
     591           1 :         dfDy = poLabel->SpacingY(bDefault);
     592             : 
     593             :         /* --------------------------------------------------------------------
     594             :          */
     595             :         /*      Escape the text, and convert to ISO8859. */
     596             :         /* --------------------------------------------------------------------
     597             :          */
     598           1 :         const char *pszText = poLabel->TextString(bDefault);
     599             : 
     600           1 :         if (pszText != nullptr && !bDefault)
     601             :         {
     602           2 :             CPLString osEscaped = TextEscape(pszText);
     603           1 :             while (osEscaped.size() > 250)
     604             :             {
     605           0 :                 WriteValue(3, osEscaped.substr(0, 250).c_str());
     606           0 :                 osEscaped.erase(0, 250);
     607             :             }
     608           1 :             WriteValue(1, osEscaped);
     609             :         }
     610             : 
     611             :         /* --------------------------------------------------------------------
     612             :          */
     613             :         /*      Store the text style in the map. */
     614             :         /* --------------------------------------------------------------------
     615             :          */
     616             :         std::map<CPLString, CPLString> oTextStyleDef =
     617           2 :             PrepareTextStyleDefinition(poLabel);
     618           2 :         CPLString osStyleName;
     619             : 
     620           1 :         for (const auto &oPair : oNewTextStyles)
     621             :         {
     622           0 :             if (oPair.second == oTextStyleDef)
     623             :             {
     624           0 :                 osStyleName = oPair.first;
     625           0 :                 break;
     626             :             }
     627             :         }
     628             : 
     629           1 :         if (osStyleName == "")
     630             :         {
     631             : 
     632           0 :             do
     633             :             {
     634           1 :                 osStyleName.Printf("AutoTextStyle-%d", nNextAutoID++);
     635           1 :             } while (poDS->oHeaderDS.TextStyleExists(osStyleName));
     636             : 
     637           1 :             oNewTextStyles[osStyleName] = std::move(oTextStyleDef);
     638             :         }
     639             : 
     640           1 :         WriteValue(7, osStyleName);
     641             :     }
     642             : 
     643           1 :     delete poTool;
     644             : 
     645             :     /* -------------------------------------------------------------------- */
     646             :     /*      Write the location.                                             */
     647             :     /* -------------------------------------------------------------------- */
     648           1 :     OGRPoint *poPoint = poFeature->GetGeometryRef()->toPoint();
     649             : 
     650           1 :     WriteValue(10, poPoint->getX() + dfDx);
     651           1 :     if (!WriteValue(20, poPoint->getY() + dfDy))
     652           0 :         return OGRERR_FAILURE;
     653             : 
     654           1 :     if (poPoint->getGeometryType() == wkbPoint25D)
     655             :     {
     656           1 :         if (!WriteValue(30, poPoint->getZ()))
     657           0 :             return OGRERR_FAILURE;
     658             :     }
     659             : 
     660           1 :     return OGRERR_NONE;
     661             : }
     662             : 
     663             : /************************************************************************/
     664             : /*                     PrepareLineTypeDefinition()                      */
     665             : /************************************************************************/
     666             : std::vector<double>
     667           6 : OGRDXFWriterLayer::PrepareLineTypeDefinition(OGRStylePen *poPen)
     668             : {
     669             : 
     670             :     /* -------------------------------------------------------------------- */
     671             :     /*      Fetch pattern.                                                  */
     672             :     /* -------------------------------------------------------------------- */
     673             :     GBool bDefault;
     674           6 :     const char *pszPattern = poPen->Pattern(bDefault);
     675             : 
     676           6 :     if (bDefault || strlen(pszPattern) == 0)
     677           1 :         return std::vector<double>();
     678             : 
     679             :     /* -------------------------------------------------------------------- */
     680             :     /*      Split into pen up / pen down bits.                              */
     681             :     /* -------------------------------------------------------------------- */
     682           5 :     char **papszTokens = CSLTokenizeString(pszPattern);
     683          10 :     std::vector<double> adfWeightTokens;
     684             : 
     685          15 :     for (int i = 0; papszTokens != nullptr && papszTokens[i] != nullptr; i++)
     686             :     {
     687          10 :         const char *pszToken = papszTokens[i];
     688          20 :         CPLString osAmount;
     689          20 :         CPLString osDXFEntry;
     690             : 
     691             :         // Split amount and unit.
     692          10 :         const char *pszUnit = pszToken;  // Used after for.
     693          48 :         for (; strchr("0123456789.", *pszUnit) != nullptr; pszUnit++)
     694             :         {
     695             :         }
     696             : 
     697          10 :         osAmount.assign(pszToken, (int)(pszUnit - pszToken));
     698             : 
     699             :         // If the unit is other than 'g' we really should be trying to
     700             :         // do some type of transformation - but what to do?  Pretty hard.
     701             : 
     702             :         // Even entries are "pen down" represented as positive in DXF.
     703             :         // "Pen up" entries (gaps) are represented as negative.
     704          10 :         if (i % 2 == 0)
     705           5 :             adfWeightTokens.push_back(CPLAtof(osAmount));
     706             :         else
     707           5 :             adfWeightTokens.push_back(-CPLAtof(osAmount));
     708             :     }
     709             : 
     710           5 :     CSLDestroy(papszTokens);
     711             : 
     712           5 :     return adfWeightTokens;
     713             : }
     714             : 
     715             : /************************************************************************/
     716             : /*                       IsLineTypeProportional()                       */
     717             : /************************************************************************/
     718             : 
     719           7 : static double IsLineTypeProportional(const std::vector<double> &adfA,
     720             :                                      const std::vector<double> &adfB)
     721             : {
     722             :     // If they are not the same length, they are not the same linetype
     723           7 :     if (adfA.size() != adfB.size())
     724           0 :         return 0.0;
     725             : 
     726             :     // Determine the proportion of the first elements
     727           7 :     const double dfRatio = (adfA[0] != 0.0) ? (adfB[0] / adfA[0]) : 0.0;
     728             : 
     729             :     // Check if all elements follow this proportionality
     730           9 :     for (size_t iIndex = 1; iIndex < adfA.size(); iIndex++)
     731           7 :         if (fabs(adfB[iIndex] - (adfA[iIndex] * dfRatio)) > 1e-6)
     732           5 :             return 0.0;
     733             : 
     734           2 :     return dfRatio;
     735             : }
     736             : 
     737             : /************************************************************************/
     738             : /*                           WritePOLYLINE()                            */
     739             : /************************************************************************/
     740             : 
     741          31 : OGRErr OGRDXFWriterLayer::WritePOLYLINE(OGRFeature *poFeature,
     742             :                                         const OGRGeometry *poGeom)
     743             : 
     744             : {
     745             :     /* -------------------------------------------------------------------- */
     746             :     /*      For now we handle multilinestrings by writing a series of       */
     747             :     /*      entities.                                                       */
     748             :     /* -------------------------------------------------------------------- */
     749          31 :     if (poGeom == nullptr)
     750          27 :         poGeom = poFeature->GetGeometryRef();
     751             : 
     752          31 :     if (poGeom->IsEmpty())
     753             :     {
     754           0 :         return OGRERR_NONE;
     755             :     }
     756             : 
     757          62 :     if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon ||
     758          31 :         wkbFlatten(poGeom->getGeometryType()) == wkbMultiLineString)
     759             :     {
     760           4 :         const OGRGeometryCollection *poGC = poGeom->toGeometryCollection();
     761           4 :         OGRErr eErr = OGRERR_NONE;
     762           8 :         for (auto &&poMember : *poGC)
     763             :         {
     764           4 :             eErr = WritePOLYLINE(poFeature, poMember);
     765           4 :             if (eErr != OGRERR_NONE)
     766           0 :                 break;
     767             :         }
     768             : 
     769           4 :         return eErr;
     770             :     }
     771             : 
     772             :     /* -------------------------------------------------------------------- */
     773             :     /*      Polygons are written with on entity per ring.                   */
     774             :     /* -------------------------------------------------------------------- */
     775          54 :     if (wkbFlatten(poGeom->getGeometryType()) == wkbPolygon ||
     776          27 :         wkbFlatten(poGeom->getGeometryType()) == wkbTriangle)
     777             :     {
     778           0 :         const OGRPolygon *poPoly = poGeom->toPolygon();
     779           0 :         OGRErr eErr = OGRERR_NONE;
     780           0 :         for (auto &&poRing : *poPoly)
     781             :         {
     782           0 :             eErr = WritePOLYLINE(poFeature, poRing);
     783           0 :             if (eErr != OGRERR_NONE)
     784           0 :                 break;
     785             :         }
     786             : 
     787           0 :         return eErr;
     788             :     }
     789             : 
     790             :     /* -------------------------------------------------------------------- */
     791             :     /*      Do we now have a geometry we can work with?                     */
     792             :     /* -------------------------------------------------------------------- */
     793          27 :     if (wkbFlatten(poGeom->getGeometryType()) != wkbLineString)
     794           0 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
     795             : 
     796          27 :     const OGRLineString *poLS = poGeom->toLineString();
     797             : 
     798             :     /* -------------------------------------------------------------------- */
     799             :     /*      Write as a lightweight polygon,                                 */
     800             :     /*       or as POLYLINE if the line contains different heights          */
     801             :     /* -------------------------------------------------------------------- */
     802          27 :     int bHasDifferentZ = FALSE;
     803          27 :     if (poLS->getGeometryType() == wkbLineString25D)
     804             :     {
     805           9 :         double z0 = poLS->getZ(0);
     806          26 :         for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
     807             :         {
     808          18 :             if (z0 != poLS->getZ(iVert))
     809             :             {
     810           1 :                 bHasDifferentZ = TRUE;
     811           1 :                 break;
     812             :             }
     813             :         }
     814             :     }
     815             : 
     816          54 :     CorePropertiesType oCoreProperties;
     817             : 
     818             :     /* -------------------------------------------------------------------- */
     819             :     /*      Do we have styling information?                                 */
     820             :     /* -------------------------------------------------------------------- */
     821          27 :     OGRStyleTool *poTool = nullptr;
     822          54 :     OGRStyleMgr oSM;
     823             : 
     824          27 :     if (poFeature->GetStyleString() != nullptr)
     825             :     {
     826           6 :         oSM.InitFromFeature(poFeature);
     827             : 
     828           6 :         if (oSM.GetPartCount() > 0)
     829           6 :             poTool = oSM.GetPart(0);
     830             :     }
     831             : 
     832             :     /* -------------------------------------------------------------------- */
     833             :     /*      Handle a PEN tool to control drawing color and width.           */
     834             :     /* -------------------------------------------------------------------- */
     835          27 :     if (poTool && poTool->GetType() == OGRSTCPen)
     836             :     {
     837           6 :         OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
     838             :         GBool bDefault;
     839           6 :         const char *pszColor = poPen->Color(bDefault);
     840           6 :         if (pszColor && !bDefault)
     841           6 :             oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
     842             :     }
     843             : 
     844          27 :     WriteValue(0, bHasDifferentZ ? "POLYLINE" : "LWPOLYLINE");
     845          27 :     WriteCore(poFeature, oCoreProperties);
     846          27 :     if (bHasDifferentZ)
     847             :     {
     848           1 :         WriteValue(100, "AcDb3dPolyline");
     849           1 :         WriteValue(10, 0.0);
     850           1 :         WriteValue(20, 0.0);
     851           1 :         WriteValue(30, 0.0);
     852             :     }
     853             :     else
     854          26 :         WriteValue(100, "AcDbPolyline");
     855          27 :     if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
     856           0 :         WriteValue(70, 1 + (bHasDifferentZ ? 8 : 0));
     857             :     else
     858          27 :         WriteValue(70, 0 + (bHasDifferentZ ? 8 : 0));
     859          27 :     if (!bHasDifferentZ)
     860          26 :         WriteValue(90, poLS->getNumPoints());
     861             :     else
     862           1 :         WriteValue(66, "1");  // Vertex Flag
     863             : 
     864             :     /* -------------------------------------------------------------------- */
     865             :     /*      Handle a PEN tool to control drawing color and width.           */
     866             :     /*      Perhaps one day also dottedness, etc.                           */
     867             :     /* -------------------------------------------------------------------- */
     868          27 :     if (poTool && poTool->GetType() == OGRSTCPen)
     869             :     {
     870           6 :         OGRStylePen *poPen = cpl::down_cast<OGRStylePen *>(poTool);
     871             :         GBool bDefault;
     872             : 
     873             :         // we want to fetch the width in ground units.
     874           6 :         poPen->SetUnit(OGRSTUGround, 1.0);
     875           6 :         const double dfWidth = poPen->Width(bDefault);
     876             : 
     877           6 :         if (!bDefault)
     878           5 :             WriteValue(370, (int)floor(dfWidth * 100 + 0.5));
     879             :     }
     880             : 
     881             :     /* -------------------------------------------------------------------- */
     882             :     /*      Do we have a Linetype for the feature?                          */
     883             :     /* -------------------------------------------------------------------- */
     884          54 :     CPLString osLineType = poFeature->GetFieldAsString("Linetype");
     885          27 :     double dfLineTypeScale = 0.0;
     886             : 
     887          27 :     bool bGotLinetype = false;
     888             : 
     889          27 :     if (!osLineType.empty())
     890             :     {
     891             :         std::vector<double> adfLineType =
     892           4 :             poDS->oHeaderDS.LookupLineType(osLineType);
     893             : 
     894           2 :         if (adfLineType.empty() && oNewLineTypes.count(osLineType) > 0)
     895           0 :             adfLineType = oNewLineTypes[osLineType];
     896             : 
     897           2 :         if (!adfLineType.empty())
     898             :         {
     899           1 :             bGotLinetype = true;
     900           1 :             WriteValue(6, osLineType);
     901             : 
     902             :             // If the given linetype is proportional to the linetype data
     903             :             // in the style string, then apply a linetype scale
     904           1 :             if (poTool != nullptr && poTool->GetType() == OGRSTCPen)
     905             :             {
     906             :                 std::vector<double> adfDefinition = PrepareLineTypeDefinition(
     907           2 :                     static_cast<OGRStylePen *>(poTool));
     908             : 
     909           1 :                 if (!adfDefinition.empty())
     910             :                 {
     911             :                     dfLineTypeScale =
     912           1 :                         IsLineTypeProportional(adfLineType, adfDefinition);
     913             : 
     914           1 :                     if (dfLineTypeScale != 0.0 &&
     915           0 :                         fabs(dfLineTypeScale - 1.0) > 1e-4)
     916             :                     {
     917           0 :                         WriteValue(48, dfLineTypeScale);
     918             :                     }
     919             :                 }
     920             :             }
     921             :         }
     922             :     }
     923             : 
     924          27 :     if (!bGotLinetype && poTool != nullptr && poTool->GetType() == OGRSTCPen)
     925             :     {
     926             :         std::vector<double> adfDefinition =
     927          10 :             PrepareLineTypeDefinition(static_cast<OGRStylePen *>(poTool));
     928             : 
     929           5 :         if (!adfDefinition.empty())
     930             :         {
     931             :             // Is this definition already created and named?
     932           7 :             for (const auto &oPair : poDS->oHeaderDS.GetLineTypeTable())
     933             :             {
     934             :                 dfLineTypeScale =
     935           4 :                     IsLineTypeProportional(oPair.second, adfDefinition);
     936           4 :                 if (dfLineTypeScale != 0.0)
     937             :                 {
     938           1 :                     osLineType = oPair.first;
     939           1 :                     break;
     940             :                 }
     941             :             }
     942             : 
     943           4 :             if (dfLineTypeScale == 0.0)
     944             :             {
     945           4 :                 for (const auto &oPair : oNewLineTypes)
     946             :                 {
     947             :                     dfLineTypeScale =
     948           2 :                         IsLineTypeProportional(oPair.second, adfDefinition);
     949           2 :                     if (dfLineTypeScale != 0.0)
     950             :                     {
     951           1 :                         osLineType = oPair.first;
     952           1 :                         break;
     953             :                     }
     954             :                 }
     955             :             }
     956             : 
     957             :             // If not, create an automatic name for it.
     958           4 :             if (osLineType == "")
     959             :             {
     960           1 :                 dfLineTypeScale = 1.0;
     961           0 :                 do
     962             :                 {
     963           1 :                     osLineType.Printf("AutoLineType-%d", nNextAutoID++);
     964           1 :                 } while (poDS->oHeaderDS.LookupLineType(osLineType).size() > 0);
     965             :             }
     966             : 
     967             :             // If it isn't already defined, add it now.
     968           7 :             if (poDS->oHeaderDS.LookupLineType(osLineType).empty() &&
     969           3 :                 oNewLineTypes.count(osLineType) == 0)
     970             :             {
     971           2 :                 oNewLineTypes[osLineType] = std::move(adfDefinition);
     972             :             }
     973             : 
     974           4 :             WriteValue(6, osLineType);
     975             : 
     976           4 :             if (dfLineTypeScale != 0.0 && fabs(dfLineTypeScale - 1.0) > 1e-4)
     977             :             {
     978           2 :                 WriteValue(48, dfLineTypeScale);
     979             :             }
     980             :         }
     981             :     }
     982             : 
     983             :     /* -------------------------------------------------------------------- */
     984             :     /*      Write the vertices                                              */
     985             :     /* -------------------------------------------------------------------- */
     986             : 
     987          27 :     if (!bHasDifferentZ && poLS->getGeometryType() == wkbLineString25D)
     988             :     {
     989             :         // if LWPOLYLINE with Z write it only once
     990           8 :         if (!WriteValue(38, poLS->getZ(0)))
     991           0 :             return OGRERR_FAILURE;
     992             :     }
     993             : 
     994          81 :     for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
     995             :     {
     996          54 :         if (bHasDifferentZ)
     997             :         {
     998           2 :             WriteValue(0, "VERTEX");
     999           2 :             WriteCore(poFeature, CorePropertiesType());
    1000           2 :             WriteValue(100, "AcDbVertex");
    1001           2 :             WriteValue(100, "AcDb3dPolylineVertex");
    1002             :         }
    1003          54 :         WriteValue(10, poLS->getX(iVert));
    1004          54 :         if (!WriteValue(20, poLS->getY(iVert)))
    1005           0 :             return OGRERR_FAILURE;
    1006             : 
    1007          54 :         if (bHasDifferentZ)
    1008             :         {
    1009           2 :             if (!WriteValue(30, poLS->getZ(iVert)))
    1010           0 :                 return OGRERR_FAILURE;
    1011           2 :             WriteValue(70, 32);
    1012             :         }
    1013             :     }
    1014             : 
    1015          27 :     if (bHasDifferentZ)
    1016             :     {
    1017           1 :         WriteValue(0, "SEQEND");
    1018           1 :         WriteCore(poFeature, CorePropertiesType());
    1019             :     }
    1020             : 
    1021          27 :     delete poTool;
    1022             : 
    1023          27 :     return OGRERR_NONE;
    1024             : 
    1025             : #ifdef notdef
    1026             :     /* -------------------------------------------------------------------- */
    1027             :     /*      Alternate unmaintained implementation as a polyline entity.     */
    1028             :     /* -------------------------------------------------------------------- */
    1029             :     WriteValue(0, "POLYLINE");
    1030             :     WriteCore(poFeature);
    1031             :     WriteValue(100, "AcDbPolyline");
    1032             :     if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
    1033             :         WriteValue(70, 1);
    1034             :     else
    1035             :         WriteValue(70, 0);
    1036             :     WriteValue(66, "1");
    1037             : 
    1038             :     for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
    1039             :     {
    1040             :         WriteValue(0, "VERTEX");
    1041             :         WriteValue(8, "0");
    1042             :         WriteValue(10, poLS->getX(iVert));
    1043             :         if (!WriteValue(20, poLS->getY(iVert)))
    1044             :             return OGRERR_FAILURE;
    1045             : 
    1046             :         if (poLS->getGeometryType() == wkbLineString25D)
    1047             :         {
    1048             :             if (!WriteValue(30, poLS->getZ(iVert)))
    1049             :                 return OGRERR_FAILURE;
    1050             :         }
    1051             :     }
    1052             : 
    1053             :     WriteValue(0, "SEQEND");
    1054             :     WriteValue(8, "0");
    1055             : 
    1056             :     return OGRERR_NONE;
    1057             : #endif
    1058             : }
    1059             : 
    1060             : /************************************************************************/
    1061             : /*                             WriteHATCH()                             */
    1062             : /************************************************************************/
    1063             : 
    1064          32 : OGRErr OGRDXFWriterLayer::WriteHATCH(OGRFeature *poFeature, OGRGeometry *poGeom)
    1065             : 
    1066             : {
    1067             :     /* -------------------------------------------------------------------- */
    1068             :     /*      For now we handle multipolygons by writing a series of          */
    1069             :     /*      entities.                                                       */
    1070             :     /* -------------------------------------------------------------------- */
    1071          32 :     if (poGeom == nullptr)
    1072          28 :         poGeom = poFeature->GetGeometryRef();
    1073             : 
    1074          32 :     if (poGeom->IsEmpty())
    1075             :     {
    1076           0 :         return OGRERR_NONE;
    1077             :     }
    1078             : 
    1079          32 :     if (wkbFlatten(poGeom->getGeometryType()) == wkbMultiPolygon)
    1080             :     {
    1081           4 :         OGRErr eErr = OGRERR_NONE;
    1082           8 :         for (auto &&poMember : poGeom->toMultiPolygon())
    1083             :         {
    1084           4 :             eErr = WriteHATCH(poFeature, poMember);
    1085           4 :             if (eErr != OGRERR_NONE)
    1086           0 :                 break;
    1087             :         }
    1088             : 
    1089           4 :         return eErr;
    1090             :     }
    1091             : 
    1092             :     /* -------------------------------------------------------------------- */
    1093             :     /*      Do we now have a geometry we can work with?                     */
    1094             :     /* -------------------------------------------------------------------- */
    1095          29 :     if (wkbFlatten(poGeom->getGeometryType()) != wkbPolygon &&
    1096           1 :         wkbFlatten(poGeom->getGeometryType()) != wkbTriangle)
    1097             :     {
    1098           0 :         return OGRERR_UNSUPPORTED_GEOMETRY_TYPE;
    1099             :     }
    1100             : 
    1101          56 :     CorePropertiesType oCoreProperties;
    1102             : 
    1103             :     /* -------------------------------------------------------------------- */
    1104             :     /*      Do we have styling information?                                 */
    1105             :     /* -------------------------------------------------------------------- */
    1106          28 :     OGRStyleTool *poTool = nullptr;
    1107          56 :     OGRStyleMgr oSM;
    1108             : 
    1109          28 :     if (poFeature->GetStyleString() != nullptr)
    1110             :     {
    1111          10 :         oSM.InitFromFeature(poFeature);
    1112             : 
    1113          10 :         if (oSM.GetPartCount() > 0)
    1114          10 :             poTool = oSM.GetPart(0);
    1115             :     }
    1116             :     // Write style brush fore color
    1117          56 :     std::string osBrushId;
    1118          28 :     std::string osBackgroundColor;
    1119          28 :     double dfSize = 1.0;
    1120          28 :     double dfAngle = 0.0;
    1121          28 :     if (poTool && poTool->GetType() == OGRSTCBrush)
    1122             :     {
    1123          10 :         OGRStyleBrush *poBrush = cpl::down_cast<OGRStyleBrush *>(poTool);
    1124             :         GBool bDefault;
    1125             : 
    1126          10 :         const char *pszBrushId = poBrush->Id(bDefault);
    1127          10 :         if (pszBrushId && !bDefault)
    1128           9 :             osBrushId = pszBrushId;
    1129             : 
    1130             :         // null brush (transparent - no fill, irrespective of fc or bc values
    1131          10 :         if (osBrushId == "ogr-brush-1")
    1132             :         {
    1133           1 :             oCoreProperties.emplace_back(PROP_RGBA_COLOR, "#00000000");
    1134             :         }
    1135             :         else
    1136             :         {
    1137           9 :             const char *pszColor = poBrush->ForeColor(bDefault);
    1138           9 :             if (pszColor != nullptr && !bDefault)
    1139           9 :                 oCoreProperties.emplace_back(PROP_RGBA_COLOR, pszColor);
    1140             : 
    1141           9 :             const char *pszBGColor = poBrush->BackColor(bDefault);
    1142           9 :             if (pszBGColor != nullptr && !bDefault)
    1143           8 :                 osBackgroundColor = pszBGColor;
    1144             :         }
    1145             : 
    1146          10 :         double dfStyleSize = poBrush->Size(bDefault);
    1147          10 :         if (!bDefault)
    1148           1 :             dfSize = dfStyleSize;
    1149             : 
    1150          10 :         double dfStyleAngle = poBrush->Angle(bDefault);
    1151          10 :         if (!bDefault)
    1152           1 :             dfAngle = dfStyleAngle;
    1153             :     }
    1154          28 :     delete poTool;
    1155             : 
    1156             :     /* -------------------------------------------------------------------- */
    1157             :     /*      Write as a hatch.                                               */
    1158             :     /* -------------------------------------------------------------------- */
    1159          28 :     WriteValue(0, "HATCH");
    1160          28 :     WriteCore(poFeature, oCoreProperties);
    1161          28 :     WriteValue(100, "AcDbHatch");
    1162             : 
    1163             :     // Figure out "average" elevation
    1164          28 :     OGREnvelope3D oEnv;
    1165          28 :     poGeom->getEnvelope(&oEnv);
    1166          28 :     WriteValue(10, 0);  // elevation point X = 0
    1167          28 :     WriteValue(20, 0);  // elevation point Y = 0
    1168             :     // elevation point Z = constant elevation
    1169          28 :     WriteValue(30, oEnv.MinZ + (oEnv.MaxZ - oEnv.MinZ) / 2);
    1170             : 
    1171          28 :     WriteValue(210, 0);    // extrusion direction X
    1172          28 :     WriteValue(220, 0);    // extrusion direction Y
    1173          28 :     WriteValue(230, 1.0);  // extrusion direction Z
    1174             : 
    1175          28 :     const char *pszPatternName = "SOLID";
    1176          28 :     double dfPatternRotation = 0;
    1177             : 
    1178             :     // Cf https://ezdxf.readthedocs.io/en/stable/tutorials/hatch.html#predefined-hatch-pattern
    1179             :     // for DXF standard hatch pattern names
    1180             : 
    1181          28 :     if (osBrushId.empty() || osBrushId == "ogr-brush-0")
    1182             :     {
    1183             :         // solid fill pattern
    1184             :     }
    1185           9 :     else if (osBrushId == "ogr-brush-2")
    1186             :     {
    1187             :         // horizontal line.
    1188           1 :         pszPatternName = "ANSI31";
    1189           1 :         dfPatternRotation = -45;
    1190             :     }
    1191           8 :     else if (osBrushId == "ogr-brush-3")
    1192             :     {
    1193             :         // vertical line.
    1194           1 :         pszPatternName = "ANSI31";
    1195           1 :         dfPatternRotation = 45;
    1196             :     }
    1197           7 :     else if (osBrushId == "ogr-brush-4")
    1198             :     {
    1199             :         // top-left to bottom-right diagonal hatch.
    1200           1 :         pszPatternName = "ANSI31";
    1201           1 :         dfPatternRotation = 90;
    1202             :     }
    1203           6 :     else if (osBrushId == "ogr-brush-5")
    1204             :     {
    1205             :         // bottom-left to top-right diagonal hatch
    1206           1 :         pszPatternName = "ANSI31";
    1207           1 :         dfPatternRotation = 0;
    1208             :     }
    1209           5 :     else if (osBrushId == "ogr-brush-6")
    1210             :     {
    1211             :         // cross hatch
    1212           1 :         pszPatternName = "ANSI37";
    1213           1 :         dfPatternRotation = 45;
    1214             :     }
    1215           4 :     else if (osBrushId == "ogr-brush-7")
    1216             :     {
    1217             :         // diagonal cross hatch
    1218           3 :         pszPatternName = "ANSI37";
    1219           3 :         dfPatternRotation = 0;
    1220             :     }
    1221             :     else
    1222             :     {
    1223             :         // solid fill pattern as a fallback
    1224             :     }
    1225             : 
    1226          28 :     dfPatternRotation += dfAngle;
    1227             : 
    1228          28 :     WriteValue(2, pszPatternName);
    1229             : 
    1230          28 :     if (EQUAL(pszPatternName, "ANSI31") || EQUAL(pszPatternName, "ANSI37"))
    1231             :     {
    1232           8 :         WriteValue(70, 0);  // pattern fill
    1233             :     }
    1234             :     else
    1235             :     {
    1236          20 :         WriteValue(70, 1);  // solid fill
    1237             :     }
    1238          28 :     WriteValue(71, 0);  // associativity
    1239             : 
    1240             : /* -------------------------------------------------------------------- */
    1241             : /*      Handle a PEN tool to control drawing color and width.           */
    1242             : /*      Perhaps one day also dottedness, etc.                           */
    1243             : /* -------------------------------------------------------------------- */
    1244             : #ifdef notdef
    1245             :     if (poTool && poTool->GetType() == OGRSTCPen)
    1246             :     {
    1247             :         OGRStylePen *poPen = (OGRStylePen *)poTool;
    1248             :         GBool bDefault;
    1249             : 
    1250             :         if (poPen->Color(bDefault) != NULL && !bDefault)
    1251             :             WriteValue(62, ColorStringToDXFColor(poPen->Color(bDefault)));
    1252             : 
    1253             :         double dfWidthInMM = poPen->Width(bDefault);
    1254             : 
    1255             :         if (!bDefault)
    1256             :             WriteValue(370, (int)floor(dfWidthInMM * 100 + 0.5));
    1257             :     }
    1258             : 
    1259             :     /* -------------------------------------------------------------------- */
    1260             :     /*      Do we have a Linetype for the feature?                          */
    1261             :     /* -------------------------------------------------------------------- */
    1262             :     CPLString osLineType = poFeature->GetFieldAsString("Linetype");
    1263             : 
    1264             :     if (!osLineType.empty() &&
    1265             :         (poDS->oHeaderDS.LookupLineType(osLineType) != NULL ||
    1266             :          oNewLineTypes.count(osLineType) > 0))
    1267             :     {
    1268             :         // Already define -> just reference it.
    1269             :         WriteValue(6, osLineType);
    1270             :     }
    1271             :     else if (poTool != NULL && poTool->GetType() == OGRSTCPen)
    1272             :     {
    1273             :         CPLString osDefinition = PrepareLineTypeDefinition(poFeature, poTool);
    1274             : 
    1275             :         if (osDefinition != "" && osLineType == "")
    1276             :         {
    1277             :             // Is this definition already created and named?
    1278             :             std::map<CPLString, CPLString>::iterator it;
    1279             : 
    1280             :             for (it = oNewLineTypes.begin(); it != oNewLineTypes.end(); it++)
    1281             :             {
    1282             :                 if ((*it).second == osDefinition)
    1283             :                 {
    1284             :                     osLineType = (*it).first;
    1285             :                     break;
    1286             :                 }
    1287             :             }
    1288             : 
    1289             :             // create an automatic name for it.
    1290             :             if (osLineType == "")
    1291             :             {
    1292             :                 do
    1293             :                 {
    1294             :                     osLineType.Printf("AutoLineType-%d", nNextAutoID++);
    1295             :                 } while (poDS->oHeaderDS.LookupLineType(osLineType) != NULL);
    1296             :             }
    1297             :         }
    1298             : 
    1299             :         // If it isn't already defined, add it now.
    1300             :         if (osDefinition != "" && oNewLineTypes.count(osLineType) == 0)
    1301             :         {
    1302             :             oNewLineTypes[osLineType] = osDefinition;
    1303             :             WriteValue(6, osLineType);
    1304             :         }
    1305             :     }
    1306             :     delete poTool;
    1307             : #endif
    1308             : 
    1309             :     /* -------------------------------------------------------------------- */
    1310             :     /*      Process the loops (rings).                                      */
    1311             :     /* -------------------------------------------------------------------- */
    1312          28 :     const OGRPolygon *poPoly = poGeom->toPolygon();
    1313             : 
    1314          28 :     WriteValue(91, poPoly->getNumInteriorRings() + 1);
    1315             : 
    1316          57 :     for (auto &&poLR : *poPoly)
    1317             :     {
    1318          29 :         WriteValue(92, 2);  // Polyline
    1319          29 :         WriteValue(72, 0);  // has bulge
    1320          29 :         WriteValue(73, 1);  // is closed
    1321          29 :         WriteValue(93, poLR->getNumPoints());
    1322             : 
    1323         170 :         for (int iVert = 0; iVert < poLR->getNumPoints(); iVert++)
    1324             :         {
    1325         141 :             WriteValue(10, poLR->getX(iVert));
    1326         141 :             WriteValue(20, poLR->getY(iVert));
    1327             :         }
    1328             : 
    1329          29 :         WriteValue(97, 0);  // 0 source boundary objects
    1330             :     }
    1331             : 
    1332          28 :     WriteValue(75, 0);  // hatch style = Hatch "odd parity" area (Normal style)
    1333          28 :     WriteValue(76, 1);  // hatch pattern type = predefined
    1334             : 
    1335          24 :     const auto roundIfClose = [](double x)
    1336             :     {
    1337          24 :         if (std::fabs(x - std::round(x)) < 1e-12)
    1338           8 :             x = std::round(x);
    1339          24 :         return x == 0 ? 0 : x;  // make sure we return positive zero
    1340             :     };
    1341             : 
    1342          28 :     if (EQUAL(pszPatternName, "ANSI31"))
    1343             :     {
    1344             :         // Single line. With dfPatternRotation=0, this is a bottom-left to top-right diagonal hatch
    1345             : 
    1346           4 :         WriteValue(52, dfPatternRotation);  // Hatch pattern angle
    1347           4 :         WriteValue(41, dfSize);             // Hatch pattern scale or spacing
    1348           4 :         WriteValue(77, 0);  // Hatch pattern double flag : 0 = not double
    1349             : 
    1350           4 :         WriteValue(78, 1);  // Number of pattern definition lines
    1351             : 
    1352           4 :         const double angle = dfPatternRotation + 45.0;
    1353           4 :         WriteValue(53, angle);  // Pattern line angle
    1354           4 :         WriteValue(43, 0.0);    // Pattern line base point, X component
    1355           4 :         WriteValue(44, 0.0);    // Pattern line base point, Y component
    1356           4 :         WriteValue(45, dfSize * 3.175 *
    1357           4 :                            roundIfClose(
    1358           4 :                                cos((angle + 90.0) / 180 *
    1359             :                                    M_PI)));  // Pattern line offset, X component
    1360           4 :         WriteValue(46, dfSize * 3.175 *
    1361           4 :                            roundIfClose(
    1362           4 :                                sin((angle + 90.0) / 180 *
    1363             :                                    M_PI)));  // Pattern line offset, Y component
    1364           4 :         WriteValue(79, 0);                   // Number of dash items
    1365             :     }
    1366          24 :     else if (EQUAL(pszPatternName, "ANSI37"))
    1367             :     {
    1368             :         // cross hatch. With dfPatternRotation=0, lines are diagonals
    1369             : 
    1370           4 :         WriteValue(52, dfPatternRotation);  // Hatch pattern angle
    1371           4 :         WriteValue(41, dfSize);             // Hatch pattern scale or spacing
    1372           4 :         WriteValue(77, 0);  // Hatch pattern double flag : 0 = not double
    1373             : 
    1374           4 :         WriteValue(78, 2);  // Number of pattern definition lines
    1375             : 
    1376           4 :         const double angle1 = dfPatternRotation + 45;
    1377           4 :         WriteValue(53, angle1);  // Pattern line angle
    1378           4 :         WriteValue(43, 0.0);     // Pattern line base point, X component
    1379           4 :         WriteValue(44, 0.0);     // Pattern line base point, Y component
    1380           4 :         WriteValue(45, dfSize * 3.175 *
    1381           4 :                            roundIfClose(
    1382           4 :                                cos((angle1 + 90.0) / 180 *
    1383             :                                    M_PI)));  // Pattern line offset, X component
    1384           4 :         WriteValue(46, dfSize * 3.175 *
    1385           4 :                            roundIfClose(
    1386           4 :                                sin((angle1 + 90.0) / 180 *
    1387             :                                    M_PI)));  // Pattern line offset, Y component
    1388           4 :         WriteValue(79, 0);                   // Number of dash items
    1389             : 
    1390           4 :         const double angle2 = dfPatternRotation + 135;
    1391           4 :         WriteValue(53, angle2);  // Pattern line angle
    1392           4 :         WriteValue(43, 0.0);     // Pattern line base point, X component
    1393           4 :         WriteValue(44, 0.0);     // Pattern line base point, Y component
    1394           4 :         WriteValue(45, dfSize * 3.175 *
    1395           4 :                            roundIfClose(
    1396           4 :                                cos((angle2 + 90.0) / 180 *
    1397             :                                    M_PI)));  // Pattern line offset, X component
    1398           4 :         WriteValue(46, dfSize * 3.175 *
    1399           4 :                            roundIfClose(
    1400           4 :                                sin((angle2 + 90.0) / 180 *
    1401             :                                    M_PI)));  // Pattern line offset, Y component
    1402           4 :         WriteValue(79, 0);                   // Number of dash items
    1403             :     }
    1404             : 
    1405          28 :     WriteValue(98, 0);  // 0 seed points
    1406             : 
    1407             :     // Deal with brush background color
    1408          28 :     if (!osBackgroundColor.empty())
    1409             :     {
    1410           8 :         bool bPerfectMatch = false;
    1411             :         int nColor =
    1412           8 :             ColorStringToDXFColor(osBackgroundColor.c_str(), bPerfectMatch);
    1413           8 :         if (nColor >= 0)
    1414             :         {
    1415           8 :             WriteValue(1001, "HATCHBACKGROUNDCOLOR");
    1416           8 :             if (bPerfectMatch)
    1417             :             {
    1418             :                 // C3 is top 8 bit means an indexed color
    1419           1 :                 unsigned nRGBColorUnsigned =
    1420           1 :                     (static_cast<unsigned>(0xC3) << 24) |
    1421           1 :                     ((nColor & 0xff) << 0);
    1422             :                 // Convert to signed (negative) value
    1423             :                 int nRGBColorSigned;
    1424           1 :                 memcpy(&nRGBColorSigned, &nRGBColorUnsigned,
    1425             :                        sizeof(nRGBColorSigned));
    1426           1 :                 WriteValue(1071, nRGBColorSigned);
    1427             :             }
    1428             :             else
    1429             :             {
    1430           7 :                 unsigned int nRed = 0;
    1431           7 :                 unsigned int nGreen = 0;
    1432           7 :                 unsigned int nBlue = 0;
    1433           7 :                 unsigned int nOpacity = 255;
    1434             : 
    1435             :                 const int nCount =
    1436           7 :                     sscanf(osBackgroundColor.c_str(), "#%2x%2x%2x%2x", &nRed,
    1437             :                            &nGreen, &nBlue, &nOpacity);
    1438           7 :                 if (nCount >= 3)
    1439             :                 {
    1440             :                     // C2 is top 8 bit means a true color
    1441           7 :                     unsigned nRGBColorUnsigned =
    1442             :                         (static_cast<unsigned>(0xC2) << 24) |
    1443           7 :                         ((nRed & 0xff) << 16) | ((nGreen & 0xff) << 8) |
    1444           7 :                         ((nBlue & 0xff) << 0);
    1445             :                     // Convert to signed (negative) value
    1446             :                     int nRGBColorSigned;
    1447           7 :                     memcpy(&nRGBColorSigned, &nRGBColorUnsigned,
    1448             :                            sizeof(nRGBColorSigned));
    1449           7 :                     WriteValue(1071, nRGBColorSigned);
    1450             :                 }
    1451             :             }
    1452             :         }
    1453             :     }
    1454             : 
    1455          28 :     return OGRERR_NONE;
    1456             : 
    1457             : #ifdef notdef
    1458             :     /* -------------------------------------------------------------------- */
    1459             :     /*      Alternate unmaintained implementation as a polyline entity.     */
    1460             :     /* -------------------------------------------------------------------- */
    1461             :     WriteValue(0, "POLYLINE");
    1462             :     WriteCore(poFeature);
    1463             :     WriteValue(100, "AcDbPolyline");
    1464             :     if (EQUAL(poGeom->getGeometryName(), "LINEARRING"))
    1465             :         WriteValue(70, 1);
    1466             :     else
    1467             :         WriteValue(70, 0);
    1468             :     WriteValue(66, "1");
    1469             : 
    1470             :     for (int iVert = 0; iVert < poLS->getNumPoints(); iVert++)
    1471             :     {
    1472             :         WriteValue(0, "VERTEX");
    1473             :         WriteValue(8, "0");
    1474             :         WriteValue(10, poLS->getX(iVert));
    1475             :         if (!WriteValue(20, poLS->getY(iVert)))
    1476             :             return OGRERR_FAILURE;
    1477             : 
    1478             :         if (poLS->getGeometryType() == wkbLineString25D)
    1479             :         {
    1480             :             if (!WriteValue(30, poLS->getZ(iVert)))
    1481             :                 return OGRERR_FAILURE;
    1482             :         }
    1483             :     }
    1484             : 
    1485             :     WriteValue(0, "SEQEND");
    1486             :     WriteValue(8, "0");
    1487             : 
    1488             :     return OGRERR_NONE;
    1489             : #endif
    1490             : }
    1491             : 
    1492             : /************************************************************************/
    1493             : /*                           ICreateFeature()                            */
    1494             : /************************************************************************/
    1495             : 
    1496         227 : OGRErr OGRDXFWriterLayer::ICreateFeature(OGRFeature *poFeature)
    1497             : 
    1498             : {
    1499         227 :     OGRGeometry *poGeom = poFeature->GetGeometryRef();
    1500         227 :     OGRwkbGeometryType eGType = wkbNone;
    1501             : 
    1502         227 :     if (poGeom != nullptr)
    1503             :     {
    1504         194 :         if (!poGeom->IsEmpty())
    1505             :         {
    1506         192 :             OGREnvelope sEnvelope;
    1507         192 :             poGeom->getEnvelope(&sEnvelope);
    1508         192 :             poDS->UpdateExtent(&sEnvelope);
    1509             :         }
    1510         194 :         eGType = wkbFlatten(poGeom->getGeometryType());
    1511             :     }
    1512             : 
    1513         227 :     if (eGType == wkbPoint)
    1514             :     {
    1515         125 :         const char *pszBlockName = poFeature->GetFieldAsString("BlockName");
    1516             : 
    1517             :         // We don't want to treat as a blocks ref if the block is not defined
    1518         250 :         if (pszBlockName &&
    1519         125 :             poDS->oHeaderDS.LookupBlock(pszBlockName) == nullptr)
    1520             :         {
    1521         130 :             if (poDS->poBlocksLayer == nullptr ||
    1522           7 :                 poDS->poBlocksLayer->FindBlock(pszBlockName) == nullptr)
    1523         118 :                 pszBlockName = nullptr;
    1524             :         }
    1525             : 
    1526         125 :         if (pszBlockName != nullptr)
    1527           7 :             return WriteINSERT(poFeature);
    1528             : 
    1529         119 :         else if (poFeature->GetStyleString() != nullptr &&
    1530           1 :                  STARTS_WITH_CI(poFeature->GetStyleString(), "LABEL"))
    1531           1 :             return WriteTEXT(poFeature);
    1532             :         else
    1533         117 :             return WritePOINT(poFeature);
    1534             :     }
    1535         102 :     else if (eGType == wkbLineString || eGType == wkbMultiLineString)
    1536          27 :         return WritePOLYLINE(poFeature);
    1537             : 
    1538          75 :     else if (eGType == wkbPolygon || eGType == wkbTriangle ||
    1539             :              eGType == wkbMultiPolygon)
    1540             :     {
    1541          28 :         if (bWriteHatch)
    1542          28 :             return WriteHATCH(poFeature);
    1543             :         else
    1544           0 :             return WritePOLYLINE(poFeature);
    1545             :     }
    1546             : 
    1547             :     // Explode geometry collections into multiple entities.
    1548          47 :     else if (eGType == wkbGeometryCollection || eGType == wkbMultiPoint)
    1549             :     {
    1550             :         OGRGeometryCollection *poGC =
    1551          13 :             poFeature->StealGeometry()->toGeometryCollection();
    1552          37 :         for (auto &&poMember : poGC)
    1553             :         {
    1554          25 :             poFeature->SetGeometry(poMember);
    1555             : 
    1556          25 :             OGRErr eErr = CreateFeature(poFeature);
    1557             : 
    1558          25 :             if (eErr != OGRERR_NONE)
    1559             :             {
    1560           1 :                 delete poGC;
    1561           1 :                 return eErr;
    1562             :             }
    1563             :         }
    1564             : 
    1565          12 :         poFeature->SetGeometryDirectly(poGC);
    1566          12 :         return OGRERR_NONE;
    1567             :     }
    1568             :     else
    1569             :     {
    1570          34 :         CPLError(CE_Failure, CPLE_AppDefined,
    1571             :                  "No known way to write feature with geometry '%s'.",
    1572             :                  OGRGeometryTypeToName(eGType));
    1573          34 :         return OGRERR_FAILURE;
    1574             :     }
    1575             : }
    1576             : 
    1577             : /************************************************************************/
    1578             : /*                       ColorStringToDXFColor()                        */
    1579             : /************************************************************************/
    1580             : 
    1581          25 : int OGRDXFWriterLayer::ColorStringToDXFColor(const char *pszRGB,
    1582             :                                              bool &bPerfectMatch)
    1583             : 
    1584             : {
    1585          25 :     bPerfectMatch = false;
    1586             : 
    1587             :     /* -------------------------------------------------------------------- */
    1588             :     /*      Parse the RGB string.                                           */
    1589             :     /* -------------------------------------------------------------------- */
    1590          25 :     if (pszRGB == nullptr)
    1591           0 :         return -1;
    1592             : 
    1593          25 :     unsigned int nRed = 0;
    1594          25 :     unsigned int nGreen = 0;
    1595          25 :     unsigned int nBlue = 0;
    1596          25 :     unsigned int nOpacity = 255;
    1597             : 
    1598             :     const int nCount =
    1599          25 :         sscanf(pszRGB, "#%2x%2x%2x%2x", &nRed, &nGreen, &nBlue, &nOpacity);
    1600             : 
    1601          25 :     if (nCount < 3)
    1602           0 :         return -1;
    1603             : 
    1604             :     /* -------------------------------------------------------------------- */
    1605             :     /*      Find near color in DXF palette.                                 */
    1606             :     /* -------------------------------------------------------------------- */
    1607          25 :     const unsigned char *pabyDXFColors = ACGetColorTable();
    1608          25 :     int nMinDist = 768;
    1609          25 :     int nBestColor = -1;
    1610             : 
    1611        3865 :     for (int i = 1; i < 256; i++)
    1612             :     {
    1613        3850 :         const int nDist =
    1614        3850 :             std::abs(static_cast<int>(nRed) - pabyDXFColors[i * 3 + 0]) +
    1615        3850 :             std::abs(static_cast<int>(nGreen) - pabyDXFColors[i * 3 + 1]) +
    1616        3850 :             std::abs(static_cast<int>(nBlue) - pabyDXFColors[i * 3 + 2]);
    1617             : 
    1618        3850 :         if (nDist < nMinDist)
    1619             :         {
    1620         205 :             nBestColor = i;
    1621         205 :             nMinDist = nDist;
    1622         205 :             if (nMinDist == 0)
    1623             :             {
    1624          10 :                 bPerfectMatch = true;
    1625          10 :                 break;
    1626             :             }
    1627             :         }
    1628             :     }
    1629             : 
    1630          25 :     return nBestColor;
    1631             : }
    1632             : 
    1633             : /************************************************************************/
    1634             : /*                             GetDataset()                             */
    1635             : /************************************************************************/
    1636             : 
    1637          17 : GDALDataset *OGRDXFWriterLayer::GetDataset()
    1638             : {
    1639          17 :     return poDS;
    1640             : }

Generated by: LCOV version 1.14