﻿/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you may find one here:
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * or you may search the http://www.gnu.org website for the version 2 license,
 * or you may write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */

// The DXF reader lib (libdxfrw) comes from dxflib project used in QCAD
// See http://www.ribbonsoft.com
// Each time a dxf entity is read, a "call back" function is called
// like void DXF_IMPORT_PLUGIN::addLine( const DL_LineData& data ) when a line is read.
// this function just add the BOARD entity from dxf parameters (start and end point ...)


#include "dxf_import_plugin.h"
#include <wx/arrstr.h>
#include <wx/regex.h>
#include <geometry/ellipse.h>
#include <bezier_curves.h>

#include <trigo.h>
#include <macros.h>
#include <cmath>    // isnan
#include <board.h>
#include "common.h"


/*
 * Important notes
 * 1. All output coordinates of this importer are in mm
 * 2. DXFs have a concept of world (WCS) and object coordinates (OCS)
   3. The following objects are world coordinates:
        - Line
        - Point
        - Polyline (3D)
        - Vertex (3D)
        - Polymesh
        - Polyface
        - Viewport
    4. The following entities are object coordinates
        - Circle
        - Arc
        - Solid
        - Trace
        - Attrib
        - Shape
        - Insert
        - Polyline (2D)
        - Vertex (2D)
        - LWPolyline
        - Hatch
        - Image
        - Text
 *   5. Object coordinates must be run through the arbitrary axis
 *      translation even though they are 2D drawings and most of the time
 *      the import is fine. Sometimes, against all logic, CAD tools like
 *      SolidWorks may randomly insert circles "mirror" that must be unflipped
 *      by following the object to world conversion
 *    6. Blocks are virtual groups, blocks must be placed by a INSERT entity
 *    7. Blocks may be repeated multiple times
 *    8. There is no sane way to make text look perfect like the original CAD.
 *       DXF simply does mpt specifying text/font enough to make it portable.
 *       We however make do try to get it somewhat close/visually appealing.
 *    9. We silently drop the z coordinate on 3d polylines
 */


// minimum bulge value before resorting to a line segment;
// the value 0.0218 is equivalent to about 5 degrees arc,
#define MIN_BULGE 0.0218

#define SCALE_FACTOR(x) (x)


DXF_IMPORT_PLUGIN::DXF_IMPORT_PLUGIN() : DL_CreationAdapter()
{
    m_xOffset          = 0.0;          // X coord offset for conversion (in mm)
    m_yOffset          = 0.0;          // Y coord offset for conversion (in mm)
    m_version          = 0;            // the dxf version, not yet used
    m_defaultThickness = 0.2;          // default thickness (in mm)
    m_brdLayer         = Dwgs_User;    // The default import layer
    m_importAsFPShapes = true;
    m_minX             = m_minY = std::numeric_limits<double>::max();
    m_maxX             = m_maxY = std::numeric_limits<double>::lowest();
    m_currentUnit      = DXF_IMPORT_UNITS::DEFAULT;

    m_importCoordinatePrecision = 4;    // initial value per dxf spec
    m_importAnglePrecision      = 0;    // initial value per dxf spec

    // placeholder layer so we can fallback to something later
    auto layer0 = std::make_unique<DXF_IMPORT_LAYER>( "", DXF_IMPORT_LINEWEIGHT_BY_LW_DEFAULT );
    m_layers.push_back( std::move( layer0 ) );

    m_currentBlock = nullptr;
}


DXF_IMPORT_PLUGIN::~DXF_IMPORT_PLUGIN()
{
}


bool DXF_IMPORT_PLUGIN::Load( const wxString& aFileName )
{
    try
    {
        return ImportDxfFile( aFileName );
    }
    catch( const std::bad_alloc& )
    {
        m_layers.clear();
        m_blocks.clear();
        m_styles.clear();

        m_internalImporter.ClearShapes();

        ReportMsg( _( "Memory was exhausted trying to load the DXF, it may be too large." ) );
        return false;
    }
}


bool DXF_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
{
    try
    {
        return ImportDxfFile( aMemBuffer );
    }
    catch( const std::bad_alloc& )
    {
        m_layers.clear();
        m_blocks.clear();
        m_styles.clear();

        m_internalImporter.ClearShapes();

        ReportMsg( _( "Memory was exhausted trying to load the DXF, it may be too large." ) );
        return false;
    }
}


bool DXF_IMPORT_PLUGIN::Import()
{
    wxCHECK( m_importer, false );
    m_internalImporter.ImportTo( *m_importer );

    return true;
}


double DXF_IMPORT_PLUGIN::GetImageWidth() const
{
    return m_maxX - m_minX;
}


double DXF_IMPORT_PLUGIN::GetImageHeight() const
{
    return m_maxY - m_minY;
}


BOX2D DXF_IMPORT_PLUGIN::GetImageBBox() const
{
    BOX2D bbox;
    bbox.SetOrigin( m_minX, m_minY );
    bbox.SetEnd( m_maxX, m_maxY );

    return bbox;
}


void DXF_IMPORT_PLUGIN::SetImporter( GRAPHICS_IMPORTER* aImporter )
{
    GRAPHICS_IMPORT_PLUGIN::SetImporter( aImporter );

    if( m_importer )
        SetDefaultLineWidthMM( m_importer->GetLineWidthMM() );
}


double DXF_IMPORT_PLUGIN::mapX( double aDxfCoordX )
{
    return SCALE_FACTOR( m_xOffset + ( aDxfCoordX * getCurrentUnitScale() ) );
}


double DXF_IMPORT_PLUGIN::mapY( double aDxfCoordY )
{
    return SCALE_FACTOR( m_yOffset - ( aDxfCoordY * getCurrentUnitScale() ) );
}


double DXF_IMPORT_PLUGIN::mapDim( double aDxfValue )
{
    return SCALE_FACTOR( aDxfValue * getCurrentUnitScale() );
}


bool DXF_IMPORT_PLUGIN::ImportDxfFile( const wxString& aFile )
{
    DL_Dxf dxf_reader;

    // wxFopen takes care of unicode filenames across platforms
    FILE* fp = wxFopen( aFile, wxT( "rt" ) );

    if( fp == nullptr )
        return false;

    // Note the dxf reader takes care of switching to "C" locale before reading the file
    // and will close the file after reading
    bool success = dxf_reader.in( fp, this );

    return success;
}


bool DXF_IMPORT_PLUGIN::ImportDxfFile( const wxMemoryBuffer& aMemBuffer )
{
    DL_Dxf dxf_reader;

    std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );

    // Note the dxf reader takes care of switching to "C" locale before reading the file
    // and will close the file after reading
    bool success = dxf_reader.in( str, this );

    return success;
}


void DXF_IMPORT_PLUGIN::ReportMsg( const wxString& aMessage )
{
    // Add message to keep trace of not handled dxf entities
    m_messages += aMessage;
    m_messages += '\n';
}


void DXF_IMPORT_PLUGIN::addSpline( const DL_SplineData& aData )
{
    // Called when starting reading a spline
    m_curr_entity.Clear();
    m_curr_entity.m_EntityParseStatus = 1;
    m_curr_entity.m_EntityFlag = aData.flags;
    m_curr_entity.m_EntityType = DL_ENTITY_SPLINE;
    m_curr_entity.m_SplineDegree = aData.degree;
    m_curr_entity.m_SplineTangentStartX = aData.tangentStartX;
    m_curr_entity.m_SplineTangentStartY = aData.tangentStartY;
    m_curr_entity.m_SplineTangentEndX = aData.tangentEndX;
    m_curr_entity.m_SplineTangentEndY = aData.tangentEndY;
    m_curr_entity.m_SplineKnotsCount = aData.nKnots;
    m_curr_entity.m_SplineControlCount = aData.nControl;
    m_curr_entity.m_SplineFitCount = aData.nFit;
}


void DXF_IMPORT_PLUGIN::addControlPoint( const DL_ControlPointData& aData )
{
    // Called for every spline control point, when reading a spline entity
    m_curr_entity.m_SplineControlPointList.emplace_back( aData.x , aData.y, aData.w );
}


void DXF_IMPORT_PLUGIN::addFitPoint( const DL_FitPointData& aData )
{
    // Called for every spline fit point, when reading a spline entity
    // we store only the X,Y coord values in a VECTOR2D
    m_curr_entity.m_SplineFitPointList.emplace_back( aData.x, aData.y );
}


void DXF_IMPORT_PLUGIN::addKnot( const DL_KnotData& aData)
{
    // Called for every spline knot value, when reading a spline entity
    m_curr_entity.m_SplineKnotsList.push_back( aData.k );
}


void DXF_IMPORT_PLUGIN::addLayer( const DL_LayerData& aData )
{
    wxString name = wxString::FromUTF8( aData.name.c_str() );

    int lw = attributes.getWidth();

    if( lw == DXF_IMPORT_LINEWEIGHT_BY_LAYER )
        lw = DXF_IMPORT_LINEWEIGHT_BY_LW_DEFAULT;

    std::unique_ptr<DXF_IMPORT_LAYER> layer = std::make_unique<DXF_IMPORT_LAYER>( name, lw );

    m_layers.push_back( std::move( layer ) );
}


void DXF_IMPORT_PLUGIN::addLinetype( const DL_LinetypeData& data )
{
#if 0
    wxString name = From_UTF8( data.name.c_str() );
    wxString description = From_UTF8( data.description.c_str() );
#endif
}


double DXF_IMPORT_PLUGIN::lineWeightToWidth( int lw, DXF_IMPORT_LAYER* aLayer )
{
    if( lw == DXF_IMPORT_LINEWEIGHT_BY_LAYER && aLayer != nullptr )
        lw = aLayer->m_lineWeight;

    // All lineweights >= 0 are always in 100ths of mm
    double mm = m_defaultThickness;

    if( lw >= 0 )
        mm = lw / 100.0;

    return SCALE_FACTOR( mm );
}


DXF_IMPORT_LAYER* DXF_IMPORT_PLUGIN::getImportLayer( const std::string& aLayerName )
{
    DXF_IMPORT_LAYER* layer     = m_layers.front().get();
    wxString          layerName = wxString::FromUTF8( aLayerName.c_str() );

    if( !layerName.IsEmpty() )
    {
        auto resultIt = std::find_if( m_layers.begin(), m_layers.end(),
                [layerName]( const auto& it )
                {
                    return it->m_layerName == layerName;
                } );

        if( resultIt != m_layers.end() )
            layer = resultIt->get();
    }

    return layer;
}


DXF_IMPORT_BLOCK* DXF_IMPORT_PLUGIN::getImportBlock( const std::string& aBlockName )
{
    DXF_IMPORT_BLOCK* block     = nullptr;
    wxString          blockName = wxString::FromUTF8( aBlockName.c_str() );

    if( !blockName.IsEmpty() )
    {
        auto resultIt = std::find_if( m_blocks.begin(), m_blocks.end(),
                [blockName]( const auto& it )
                {
                    return it->m_name == blockName;
                } );

        if( resultIt != m_blocks.end() )
            block = resultIt->get();
    }

    return block;
}


DXF_IMPORT_STYLE* DXF_IMPORT_PLUGIN::getImportStyle( const std::string& aStyleName )
{
    DXF_IMPORT_STYLE* style     = nullptr;
    wxString          styleName = wxString::FromUTF8( aStyleName.c_str() );

    if( !styleName.IsEmpty() )
    {
        auto resultIt = std::find_if( m_styles.begin(), m_styles.end(),
                [styleName]( const auto& it )
                {
                    return it->m_name == styleName;
                } );

        if( resultIt != m_styles.end() )
            style = resultIt->get();
    }

    return style;
}


void DXF_IMPORT_PLUGIN::addLine( const DL_LineData& aData )
{
    DXF_IMPORT_LAYER* layer     = getImportLayer( attributes.getLayer() );
    double            lineWidth = lineWeightToWidth( attributes.getWidth(), layer );

    VECTOR2D start( mapX( aData.x1 ), mapY( aData.y1 ) );
    VECTOR2D end( mapX( aData.x2 ), mapY( aData.y2 ) );

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddLine( start, end, lineWidth );

    updateImageLimits( start );
    updateImageLimits( end );
}


void DXF_IMPORT_PLUGIN::addPolyline(const DL_PolylineData& aData )
{
    // Convert DXF Polylines into a series of KiCad Lines and Arcs.
    // A Polyline (as opposed to a LWPolyline) may be a 3D line or
    // even a 3D Mesh. The only type of Polyline which is guaranteed
    // to import correctly is a 2D Polyline in X and Y, which is what
    // we assume of all Polylines. The width used is the width of the Polyline.
    // per-vertex line widths, if present, are ignored.
    m_curr_entity.Clear();
    m_curr_entity.m_EntityParseStatus = 1;
    m_curr_entity.m_EntityFlag = aData.flags;
    m_curr_entity.m_EntityType = DL_ENTITY_POLYLINE;
}


void DXF_IMPORT_PLUGIN::addVertex( const DL_VertexData& aData )
{
    if( m_curr_entity.m_EntityParseStatus == 0 )
        return;     // Error

    DXF_IMPORT_LAYER* layer     = getImportLayer( attributes.getLayer() );
    double lineWidth = lineWeightToWidth( attributes.getWidth(), layer );

    /* support for per-vertex-encoded linewidth (Cadence uses it) */
    /* linewidths are scaled by 100 in DXF */
    if( aData.startWidth > 0.0 )
        lineWidth = aData.startWidth / 100.0;
    else if ( aData.endWidth > 0.0 )
        lineWidth = aData.endWidth / 100.0;

    const DL_VertexData* vertex = &aData;

    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D vertexCoords = ocsToWcs( arbAxis, VECTOR3D( vertex->x, vertex->y, vertex->z ) );

    if( m_curr_entity.m_EntityParseStatus == 1 )    // This is the first vertex of an entity
    {
        m_curr_entity.m_LastCoordinate.x  = mapX( vertexCoords.x );
        m_curr_entity.m_LastCoordinate.y  = mapY( vertexCoords.y );
        m_curr_entity.m_PolylineStart = m_curr_entity.m_LastCoordinate;
        m_curr_entity.m_BulgeVertex = vertex->bulge;
        m_curr_entity.m_EntityParseStatus = 2;
        return;
    }

    VECTOR2D seg_end( mapX( vertexCoords.x ), mapY( vertexCoords.y ) );

    if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE )
        insertLine( m_curr_entity.m_LastCoordinate, seg_end, lineWidth );
    else
        insertArc( m_curr_entity.m_LastCoordinate, seg_end, m_curr_entity.m_BulgeVertex,
                   lineWidth );

    m_curr_entity.m_LastCoordinate = seg_end;
    m_curr_entity.m_BulgeVertex = vertex->bulge;
}


void DXF_IMPORT_PLUGIN::endEntity()
{
    DXF_IMPORT_LAYER* layer     = getImportLayer( attributes.getLayer() );
    double            lineWidth = lineWeightToWidth( attributes.getWidth(), layer );

    if( m_curr_entity.m_EntityType == DL_ENTITY_POLYLINE ||
        m_curr_entity.m_EntityType == DL_ENTITY_LWPOLYLINE )
    {
        // Polyline flags bit 0 indicates closed (1) or open (0) polyline
        if( m_curr_entity.m_EntityFlag & 1 )
        {
            if( std::abs( m_curr_entity.m_BulgeVertex ) < MIN_BULGE )
            {
                insertLine( m_curr_entity.m_LastCoordinate, m_curr_entity.m_PolylineStart,
                            lineWidth );
            }
            else
            {
                insertArc( m_curr_entity.m_LastCoordinate, m_curr_entity.m_PolylineStart,
                           m_curr_entity.m_BulgeVertex, lineWidth );
            }
        }
    }

    if( m_curr_entity.m_EntityType == DL_ENTITY_SPLINE )
        insertSpline( lineWidth );

    m_curr_entity.Clear();
}


void DXF_IMPORT_PLUGIN::addBlock( const DL_BlockData& aData )
{
    wxString name = wxString::FromUTF8( aData.name.c_str() );

    std::unique_ptr<DXF_IMPORT_BLOCK> block = std::make_unique<DXF_IMPORT_BLOCK>( name, aData.bpx,
                                                                                  aData.bpy );

    m_blocks.push_back( std::move( block ) );

    m_currentBlock = m_blocks.back().get();
}


void DXF_IMPORT_PLUGIN::endBlock()
{
    m_currentBlock = nullptr;
}

void DXF_IMPORT_PLUGIN::addInsert( const DL_InsertData& aData )
{
    DXF_IMPORT_BLOCK* block = getImportBlock( aData.name );

    if( block == nullptr )
        return;

    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );

    MATRIX3x3D rot;
    rot.SetRotation( DEG2RAD( -aData.angle ) ); // DL_InsertData angle is in degrees

    MATRIX3x3D scale;
    scale.SetScale( VECTOR2D( aData.sx, aData.sy ) );

    MATRIX3x3D trans = ( arbAxis * rot ) * scale;
    VECTOR3D insertCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );

    VECTOR2D translation( mapX( insertCoords.x ), mapY( insertCoords.y ) );
    translation -= VECTOR2D( mapX( block->m_baseX ), mapY( block->m_baseY ) );

    for( const std::unique_ptr<IMPORTED_SHAPE>& shape : block->m_buffer.GetShapes() )
    {
        std::unique_ptr<IMPORTED_SHAPE> newShape = shape->clone();

        newShape->Transform( trans, translation );

        m_internalImporter.AddShape( newShape );
    }
}


void DXF_IMPORT_PLUGIN::addCircle( const DL_CircleData& aData )
{
    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D   centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );

    VECTOR2D          center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
    DXF_IMPORT_LAYER* layer     = getImportLayer( attributes.getLayer() );
    double            lineWidth = lineWeightToWidth( attributes.getWidth(), layer );

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddCircle( center, mapDim( aData.radius ), lineWidth, false );

    VECTOR2D radiusDelta( mapDim( aData.radius ), mapDim( aData.radius ) );

    updateImageLimits( center + radiusDelta );
    updateImageLimits( center - radiusDelta );
}


void DXF_IMPORT_PLUGIN::addArc( const DL_ArcData& aData )
{
    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D   centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );

    // Init arc centre:
    VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );

    // aData.anglex is in degrees.
    EDA_ANGLE  startangle( aData.angle1, DEGREES_T );
    EDA_ANGLE  endangle( aData.angle2, DEGREES_T );

    if( ( arbAxis.GetScale().x < 0 ) != ( arbAxis.GetScale().y < 0 ) )
    {
        startangle = ANGLE_180 - startangle;
        endangle = ANGLE_180 - endangle;
        std::swap( startangle, endangle );
    }

    // Init arc start point
    VECTOR2D startPoint( aData.radius, 0.0 );
    RotatePoint( startPoint, -startangle );
    VECTOR2D arcStart( mapX( startPoint.x + centerCoords.x ),
                       mapY( startPoint.y + centerCoords.y ) );

    // calculate arc angle (arcs are CCW, and should be < 0 in Pcbnew)
    EDA_ANGLE angle = -( endangle - startangle );

    if( angle > ANGLE_0 )
        angle -= ANGLE_360;

    DXF_IMPORT_LAYER* layer     = getImportLayer( attributes.getLayer() );
    double            lineWidth = lineWeightToWidth( attributes.getWidth(), layer );

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddArc( center, arcStart, angle, lineWidth );

    VECTOR2D radiusDelta( mapDim( aData.radius ), mapDim( aData.radius ) );

    updateImageLimits( center + radiusDelta );
    updateImageLimits( center - radiusDelta );
}


void DXF_IMPORT_PLUGIN::addEllipse( const DL_EllipseData& aData )
{
    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D   centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.cx, aData.cy, aData.cz ) );
    VECTOR3D   majorCoords = ocsToWcs( arbAxis, VECTOR3D( aData.mx, aData.my, aData.mz ) );

    // DXF ellipses store the minor axis length as a ratio to the major axis.
    // The major coords are relative to the center point.
    // For now, we assume ellipses in the XY plane.

    VECTOR2D center( mapX( centerCoords.x ), mapY( centerCoords.y ) );
    VECTOR2D major( mapX( majorCoords.x ), mapY( majorCoords.y ) );

    // DXF elliptical arcs store their angles in radians (unlike circular arcs which use degrees)
    // The arcs wind CCW as in KiCad.  The end angle must be greater than the start angle, and if
    // the extrusion direction is negative, the arc winding is CW instead!  Note that this is a
    // simplification that assumes the DXF is representing a 2D drawing, and would need to be
    // revisited if we want to import true 3D drawings and "flatten" them to the 2D KiCad plane
    // internally.
    EDA_ANGLE startAngle( aData.angle1, RADIANS_T );
    EDA_ANGLE endAngle( aData.angle2, RADIANS_T );

    if( startAngle > endAngle )
        endAngle += ANGLE_360;

    if( aData.ratio == 1.0 )
    {
        double radius = major.EuclideanNorm();

        if( startAngle == endAngle )
        {
            DL_CircleData circle( aData.cx, aData.cy, aData.cz, radius );
            addCircle( circle );
            return;
        }
        else
        {
            // Angles are relative to major axis
            startAngle -= EDA_ANGLE( major );
            endAngle -= EDA_ANGLE( major );

            DL_ArcData arc( aData.cx, aData.cy, aData.cz, radius, startAngle.AsDegrees(),
                            endAngle.AsDegrees() );
            addArc( arc );
            return;
        }
    }

    // TODO: testcases for negative extrusion vector; handle it here

    std::vector<BEZIER<double>> splines;
    ELLIPSE<double> ellipse( center, major, aData.ratio, startAngle, endAngle );

    TransformEllipseToBeziers( ellipse, splines );

    DXF_IMPORT_LAYER* layer     = getImportLayer( attributes.getLayer() );
    double            lineWidth = lineWeightToWidth( attributes.getWidth(), layer );

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;

    for( const BEZIER<double>& b : splines )
        bufferToUse->AddSpline( b.Start, b.C1, b.C2, b.End, lineWidth );

    // Naive bounding
    updateImageLimits( center + major );
    updateImageLimits( center - major );
}


void DXF_IMPORT_PLUGIN::addText( const DL_TextData& aData )
{
    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D refPointCoords    = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
    VECTOR3D   secPointCoords =
            ocsToWcs( arbAxis, VECTOR3D( std::isnan( aData.apx ) ? 0 : aData.apx,
                                         std::isnan( aData.apy ) ? 0 : aData.apy,
                                         std::isnan( aData.apz ) ? 0 : aData.apz ) );

    VECTOR2D refPoint( mapX( refPointCoords.x ), mapY( refPointCoords.y ) );
    VECTOR2D secPoint( mapX( secPointCoords.x ), mapY( secPointCoords.y ) );

    if( aData.vJustification != 0 || aData.hJustification != 0 || aData.hJustification == 4 )
    {
        if( aData.hJustification != 3 && aData.hJustification != 5 )
        {
            VECTOR2D tmp = secPoint;
            secPoint = refPoint;
            refPoint = tmp;
        }
    }

    wxString text = toNativeString( wxString::FromUTF8( aData.text.c_str() ) );

    DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() );

    double textHeight = mapDim( aData.height );

    // The 0.9 factor gives a better height/width base ratio with our font
    double charWidth = textHeight * 0.9;

    if( style != nullptr )
        charWidth *= style->m_widthFactor;

    double textWidth = charWidth * text.length();   // Rough approximation
    double textThickness = textHeight / 8.0;        // Use a reasonable line thickness for this text

    VECTOR2D bottomLeft( 0.0, 0.0 );
    VECTOR2D bottomRight( 0.0, 0.0 );
    VECTOR2D topLeft( 0.0, 0.0 );
    VECTOR2D topRight( 0.0, 0.0 );

    GR_TEXT_H_ALIGN_T hJustify = GR_TEXT_H_ALIGN_LEFT;
    GR_TEXT_V_ALIGN_T vJustify = GR_TEXT_V_ALIGN_BOTTOM;

    switch( aData.vJustification )
    {
    case 0: //DRW_Text::VBaseLine:
    case 1: //DRW_Text::VBottom:
        vJustify = GR_TEXT_V_ALIGN_BOTTOM;

        topLeft.y = textHeight;
        topRight.y = textHeight;
        break;

    case 2: //DRW_Text::VMiddle:
        vJustify = GR_TEXT_V_ALIGN_CENTER;

        bottomRight.y = -textHeight / 2.0;
        bottomLeft.y = -textHeight / 2.0;
        topLeft.y = textHeight / 2.0;
        topRight.y = textHeight / 2.0;
        break;

    case 3: //DRW_Text::VTop:
        vJustify = GR_TEXT_V_ALIGN_TOP;

        bottomLeft.y = -textHeight;
        bottomRight.y = -textHeight;
        break;
    }

    switch( aData.hJustification )
    {
    case 0: //DRW_Text::HLeft:
    case 3: //DRW_Text::HAligned:    // no equivalent options in text pcb.
    case 5: //DRW_Text::HFit:       // no equivalent options in text pcb.
        hJustify = GR_TEXT_H_ALIGN_LEFT;

        bottomRight.x = textWidth;
        topRight.x = textWidth;
        break;

    case 1: //DRW_Text::HCenter:
    case 4: //DRW_Text::HMiddle:     // no equivalent options in text pcb.
        hJustify = GR_TEXT_H_ALIGN_CENTER;

        bottomLeft.x = -textWidth / 2.0;
        topLeft.x = -textWidth / 2.0;
        bottomRight.x = textWidth / 2.0;
        topRight.x = textWidth / 2.0;
        break;

    case 2: //DRW_Text::HRight:
        hJustify = GR_TEXT_H_ALIGN_RIGHT;

        bottomLeft.x = -textWidth;
        topLeft.x = -textWidth;
        break;
    }

#if 0
    wxString sty = wxString::FromUTF8( aData.style.c_str() );
    sty = sty.ToLower();

    if( aData.textgen == 2 )
    {
        // Text dir = left to right;
    } else if( aData.textgen == 4 )
    {
        // Text dir = top to bottom;
    } else
    {
    }
#endif

    // dxf_lib imports text angle in radians (although there are no comment about that.
    // So, for the moment, convert this angle to degrees
    double angle_degree = aData.angle * 180 / M_PI;

    // We also need the angle in radians. so convert angle_degree to radians
    // regardless the aData.angle unit
    double angleInRads = angle_degree * M_PI / 180.0;
    double cosine = cos(angleInRads);
    double sine = sin(angleInRads);

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddText( refPoint, text, textHeight, charWidth, textThickness, angle_degree,
                          hJustify, vJustify );

    // Calculate the boundary box and update the image limits:
    bottomLeft.x = bottomLeft.x * cosine - bottomLeft.y * sine;
    bottomLeft.y = bottomLeft.x * sine + bottomLeft.y * cosine;

    bottomRight.x = bottomRight.x * cosine - bottomRight.y * sine;
    bottomRight.y = bottomRight.x * sine + bottomRight.y * cosine;

    topLeft.x = topLeft.x * cosine - topLeft.y * sine;
    topLeft.y = topLeft.x * sine + topLeft.y * cosine;

    topRight.x = topRight.x * cosine - topRight.y * sine;
    topRight.y = topRight.x * sine + topRight.y * cosine;

    bottomLeft += refPoint;
    bottomRight += refPoint;
    topLeft += refPoint;
    topRight += refPoint;

    updateImageLimits( bottomLeft );
    updateImageLimits( bottomRight );
    updateImageLimits( topLeft );
    updateImageLimits( topRight );
}


void DXF_IMPORT_PLUGIN::addMTextChunk( const std::string& text )
{
    // If the text string is greater than 250 characters, the string is divided into 250-character
    // chunks, which appear in one or more group 3 codes. If group 3 codes are used, the last group
    // is a group 1 and has fewer than 250 characters

    m_mtextContent.append( text );
}


void DXF_IMPORT_PLUGIN::addMText( const DL_MTextData& aData )
{
    m_mtextContent.append( aData.text );

    // TODO: determine control codes applied to the whole text?
    wxString text = toNativeString( wxString::FromUTF8( m_mtextContent.c_str() ) );

    DXF_IMPORT_STYLE* style = getImportStyle( aData.style.c_str() );
    double            textHeight = mapDim( aData.height );

    // The 0.9 factor gives a better height/width base ratio with our font
    double charWidth = textHeight * 0.9;

    if( style != nullptr )
        charWidth *= style->m_widthFactor;

    double textWidth = charWidth * text.length();   // Rough approximation
    double textThickness = textHeight/8.0;          // Use a reasonable line thickness for this text

    VECTOR2D bottomLeft(0.0, 0.0);
    VECTOR2D bottomRight(0.0, 0.0);
    VECTOR2D topLeft(0.0, 0.0);
    VECTOR2D topRight(0.0, 0.0);

    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D   textposCoords = ocsToWcs( arbAxis, VECTOR3D( aData.ipx, aData.ipy, aData.ipz ) );
    VECTOR2D   textpos( mapX( textposCoords.x ), mapY( textposCoords.y ) );

    // Initialize text justifications:
    GR_TEXT_H_ALIGN_T hJustify = GR_TEXT_H_ALIGN_LEFT;
    GR_TEXT_V_ALIGN_T vJustify = GR_TEXT_V_ALIGN_BOTTOM;

    if( aData.attachmentPoint <= 3 )
    {
        vJustify = GR_TEXT_V_ALIGN_TOP;

        bottomLeft.y = -textHeight;
        bottomRight.y = -textHeight;
    }
    else if( aData.attachmentPoint <= 6 )
    {
        vJustify = GR_TEXT_V_ALIGN_CENTER;

        bottomRight.y = -textHeight / 2.0;
        bottomLeft.y = -textHeight / 2.0;
        topLeft.y = textHeight / 2.0;
        topRight.y = textHeight / 2.0;
    }
    else
    {
        vJustify = GR_TEXT_V_ALIGN_BOTTOM;

        topLeft.y = textHeight;
        topRight.y = textHeight;
    }

    if( aData.attachmentPoint % 3 == 1 )
    {
        hJustify = GR_TEXT_H_ALIGN_LEFT;

        bottomRight.x = textWidth;
        topRight.x = textWidth;
    }
    else if( aData.attachmentPoint % 3 == 2 )
    {
        hJustify = GR_TEXT_H_ALIGN_CENTER;

        bottomLeft.x = -textWidth / 2.0;
        topLeft.x = -textWidth / 2.0;
        bottomRight.x = textWidth / 2.0;
        topRight.x = textWidth / 2.0;
    }
    else
    {
        hJustify = GR_TEXT_H_ALIGN_RIGHT;

        bottomLeft.x = -textWidth;
        topLeft.x = -textWidth;
    }

#if 0   // These setting have no meaning in Pcbnew
    if( data.alignH == 1 )
    {
        // Text is left to right;
    }
    else if( data.alignH == 3 )
    {
        // Text is top to bottom;
    }
    else
    {
        // use ByStyle;
    }

    if( aData.alignV == 1 )
    {
        // use AtLeast;
    }
    else
    {
        // useExact;
    }
#endif

    // dxf_lib imports text angle in radians (although there are no comment about that.
    // So, for the moment, convert this angle to degrees
    double angle_degree = aData.angle * 180/M_PI;

    // We also need the angle in radians. so convert angle_degree to radians
    // regardless the aData.angle unit
    double angleInRads = angle_degree * M_PI / 180.0;
    double cosine = cos(angleInRads);
    double sine = sin(angleInRads);


    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddText( textpos, text, textHeight, charWidth, textThickness, angle_degree,
                          hJustify, vJustify );

    bottomLeft.x = bottomLeft.x * cosine - bottomLeft.y * sine;
    bottomLeft.y = bottomLeft.x * sine + bottomLeft.y * cosine;

    bottomRight.x = bottomRight.x * cosine - bottomRight.y * sine;
    bottomRight.y = bottomRight.x * sine + bottomRight.y * cosine;

    topLeft.x = topLeft.x * cosine - topLeft.y * sine;
    topLeft.y = topLeft.x * sine + topLeft.y * cosine;

    topRight.x = topRight.x * cosine - topRight.y * sine;
    topRight.y = topRight.x * sine + topRight.y * cosine;

    bottomLeft += textpos;
    bottomRight += textpos;
    topLeft += textpos;
    topRight += textpos;

    updateImageLimits( bottomLeft );
    updateImageLimits( bottomRight );
    updateImageLimits( topLeft );
    updateImageLimits( topRight );

    m_mtextContent.clear();
}


double DXF_IMPORT_PLUGIN::getCurrentUnitScale()
{
    double scale = 1.0;

    switch( m_currentUnit )
    {
    case DXF_IMPORT_UNITS::INCH:        scale = 25.4;    break;
    case DXF_IMPORT_UNITS::FEET:        scale = 304.8;   break;
    case DXF_IMPORT_UNITS::MM:          scale = 1.0;     break;
    case DXF_IMPORT_UNITS::CM:          scale = 10.0;    break;
    case DXF_IMPORT_UNITS::METERS:      scale = 1000.0;  break;
    case DXF_IMPORT_UNITS::MICROINCHES: scale = 2.54e-5; break;
    case DXF_IMPORT_UNITS::MILS:        scale = 0.0254;  break;
    case DXF_IMPORT_UNITS::YARDS:       scale = 914.4;   break;
    case DXF_IMPORT_UNITS::ANGSTROMS:   scale = 1.0e-7;  break;
    case DXF_IMPORT_UNITS::NANOMETERS:  scale = 1.0e-6;  break;
    case DXF_IMPORT_UNITS::MICRONS:     scale = 1.0e-3;  break;
    case DXF_IMPORT_UNITS::DECIMETERS:  scale = 100.0;   break;

    default:
        // use the default of 1.0 for:
        // 0: Unspecified Units
        // 3: miles
        // 7: kilometers
        // 15: decameters
        // 16: hectometers
        // 17: gigameters
        // 18: AU
        // 19: lightyears
        // 20: parsecs
        break;
    }

    return scale;
}


void DXF_IMPORT_PLUGIN::setVariableInt( const std::string& key, int value, int code )
{
    // Called for every int variable in the DXF file (e.g. "$INSUNITS").

    if( key == "$DWGCODEPAGE" )
    {
        m_codePage = value;
        return;
    }

    if( key == "$AUPREC" )
    {
        m_importAnglePrecision = value;
        return;
    }

    if( key == "$LUPREC" )
    {
        m_importCoordinatePrecision = value;
        return;
    }

    if( key == "$INSUNITS" )    // Drawing units
    {
        m_currentUnit = DXF_IMPORT_UNITS::DEFAULT;

        switch( value )
        {
        case 1:  m_currentUnit = DXF_IMPORT_UNITS::INCH;        break;
        case 2:  m_currentUnit = DXF_IMPORT_UNITS::FEET;        break;
        case 4:  m_currentUnit = DXF_IMPORT_UNITS::MM;          break;
        case 5:  m_currentUnit = DXF_IMPORT_UNITS::CM;          break;
        case 6:  m_currentUnit = DXF_IMPORT_UNITS::METERS;      break;
        case 8:  m_currentUnit = DXF_IMPORT_UNITS::MICROINCHES; break;
        case 9:  m_currentUnit = DXF_IMPORT_UNITS::MILS;        break;
        case 10: m_currentUnit = DXF_IMPORT_UNITS::YARDS;       break;
        case 11: m_currentUnit = DXF_IMPORT_UNITS::ANGSTROMS;   break;
        case 12: m_currentUnit = DXF_IMPORT_UNITS::NANOMETERS;  break;
        case 13: m_currentUnit = DXF_IMPORT_UNITS::MICRONS;     break;
        case 14: m_currentUnit = DXF_IMPORT_UNITS::DECIMETERS;  break;

        default:
            // use the default for:
            // 0: Unspecified Units
            // 3: miles
            // 7: kilometers
            // 15: decameters
            // 16: hectometers
            // 17: gigameters
            // 18: AU
            // 19: lightyears
            // 20: parsecs
            break;
        }

    return;
    }
}


void DXF_IMPORT_PLUGIN::setVariableString( const std::string& key, const std::string& value,
                                           int code )
{
    // Called for every string variable in the DXF file (e.g. "$ACADVER").
}


wxString DXF_IMPORT_PLUGIN::toDxfString( const wxString& aStr )
{
    wxString    res;
    int         j = 0;

    for( unsigned i = 0; i<aStr.length(); ++i )
    {
        int c = aStr[i];

        if( c > 175 || c < 11 )
        {
            res.append( aStr.Mid( j, i - j ) );
            j = i;

            switch( c )
            {
            case 0x0A:
                res += wxT( "\\P" );
                break;

                // diameter:
#ifdef _WIN32
            // windows, as always, is special.
            case 0x00D8:
#else
            case 0x2205:
#endif
                res += wxT( "%%C" );
                break;

            // degree:
            case 0x00B0:
                res += wxT( "%%D" );
                break;

            // plus/minus
            case 0x00B1:
                res += wxT( "%%P" );
                break;

            default:
                j--;
                break;
            }

            j++;
        }
    }

    res.append( aStr.Mid( j ) );
    return res;
}


wxString DXF_IMPORT_PLUGIN::toNativeString( const wxString& aData )
{
    wxString res;
    size_t   i = 0;
    int      braces = 0;
    int      overbarLevel = -1;

    // For description, see:
    // https://ezdxf.readthedocs.io/en/stable/dxfinternals/entities/mtext.html
    // https://www.cadforum.cz/en/text-formatting-codes-in-mtext-objects-tip8640

    for( i = 0; i < aData.length(); i++ )
    {
        switch( (wchar_t) aData[i] )
        {
        case '{': // Text area influenced by the code
            braces++;
            break;

        case '}':
            if( overbarLevel == braces )
            {
                res << '}';
                overbarLevel = -1;
            }
            braces--;
            break;

        case '^': // C0 control code
            if( ++i >= aData.length() )
                break;

            switch( (wchar_t) aData[i] )
            {
            case 'I': res << '\t'; break;
            case 'J': res << '\b'; break;
            case ' ': res << '^'; break;
            default: break;
            }
            break;

        case '\\':
        {
            if( ++i >= aData.length() )
                break;

            switch( (wchar_t) aData[i] )
            {
            case 'P': // New paragraph (new line)
            case 'X': // Paragraph wrap on the dimension line (only in dimensions)
                res << '\n';
                break;

            case '~': // Non-wrapping space, hard space
                res << L'\u00A0';
                break;

            case 'U': // Unicode character, e.g. \U+ff08
            {
                i += 2;
                wxString codeHex;

                for( ; codeHex.length() < 4 && i < aData.length(); i++ )
                    codeHex << aData[i];

                unsigned long codeVal = 0;

                if( codeHex.ToCULong( &codeVal, 16 ) && codeVal != 0 )
                    res << wxUniChar( codeVal );

                i--;
            }
            break;

            case 'S': // Stacking
            {
                i++;
                wxString stacked;

                for( ; i < aData.length(); i++ )
                {
                    if( aData[i] == ';' )
                        break;
                    else
                        stacked << aData[i];
                }

                if( stacked.Contains( wxS( "#" ) ) )
                {
                    res << '^' << '{';
                    res << stacked.BeforeFirst( '#' );
                    res << '}' << '/' << '_' << '{';
                    res << stacked.AfterFirst( '#' );
                    res << '}';
                }
                else
                {
                    stacked.Replace( wxS( "^ " ), wxS( "/" ) );
                    res << stacked;
                }
            }
            break;

            case 'O': // Start overstrike
                if( overbarLevel == -1 )
                {
                    res << '~' << '{';
                    overbarLevel = braces;
                }
                break;
            case 'o': // Stop overstrike
                if( overbarLevel == braces )
                {
                    res << '}';
                    overbarLevel = -1;
                }
                break;

            case 'L': // Start underline
            case 'l': // Stop underline
            case 'K': // Start strike-through
            case 'k': // Stop strike-through
            case 'N': // New column
                // Ignore
                break;

            case 'p': // Control codes for bullets, numbered paragraphs, tab stops and columns
            case 'Q': // Slanting (obliquing) text by angle
            case 'H': // Text height
            case 'W': // Text width
            case 'F': // Font selection
            case 'f': // Font selection (alternative)
            case 'A': // Alignment
            case 'C': // Color change (ACI colors)
            case 'c': // Color change (truecolor)
            case 'T': // Tracking, char.spacing
                // Skip to ;
                for( ; i < aData.length(); i++ )
                {
                    if( aData[i] == ';' )
                        break;
                }
                break;

            default: // Escaped character
                if( ++i >= aData.length() )
                    break;

                res << aData[i];
                break;
            }
        }
        break;

        default: res << aData[i];
        }
    }

    if( overbarLevel != -1 )
    {
        res << '}';
        overbarLevel = -1;
    }

#if 1
    wxRegEx regexp;

    // diameter:
    regexp.Compile( wxT( "%%[cC]" ) );
#ifdef __WINDOWS__
    // windows, as always, is special.
    regexp.Replace( &res, wxChar( 0xD8 ) );
#else
    // Empty_set, diameter is 0x2300
    regexp.Replace( &res, wxChar( 0x2205 ) );
#endif

    // degree:
    regexp.Compile( wxT( "%%[dD]" ) );
    regexp.Replace( &res, wxChar( 0x00B0 ) );

    // plus/minus
    regexp.Compile( wxT( "%%[pP]" ) );
    regexp.Replace( &res, wxChar( 0x00B1 ) );
#endif

    return res;
}


void DXF_IMPORT_PLUGIN::addTextStyle( const DL_StyleData& aData )
{
    wxString name = wxString::FromUTF8( aData.name.c_str() );

    auto style = std::make_unique<DXF_IMPORT_STYLE>( name, aData.fixedTextHeight, aData.widthFactor,
                                                     aData.bold, aData.italic );

    m_styles.push_back( std::move( style ) );
}


void DXF_IMPORT_PLUGIN::addPoint( const DL_PointData& aData )
{
    MATRIX3x3D arbAxis = getArbitraryAxis( getExtrusion() );
    VECTOR3D   centerCoords = ocsToWcs( arbAxis, VECTOR3D( aData.x, aData.y, aData.z ) );
    VECTOR2D   center( mapX( centerCoords.x ), mapY( centerCoords.y ) );

    // we emulate points with filled circles
    // set the linewidth to something that even small circles look good with
    // thickness is optional for dxf points
    // note: we had to modify the dxf library to grab the attribute for thickness
    double lineWidth = 0.0001;
    double thickness = mapDim( std::max( aData.thickness, 0.01 ) );

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddCircle( center, thickness, lineWidth, true );

    VECTOR2D radiusDelta( SCALE_FACTOR( thickness ), SCALE_FACTOR( thickness ) );

    updateImageLimits( center + radiusDelta );
    updateImageLimits( center - radiusDelta );
}


void DXF_IMPORT_PLUGIN::insertLine( const VECTOR2D& aSegStart,
                                    const VECTOR2D& aSegEnd, double aWidth )
{
    VECTOR2D origin( SCALE_FACTOR( aSegStart.x ), SCALE_FACTOR( aSegStart.y ) );
    VECTOR2D end( SCALE_FACTOR( aSegEnd.x ), SCALE_FACTOR( aSegEnd.y ) );

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddLine( origin, end, aWidth );

    updateImageLimits( origin );
    updateImageLimits( end );
}


void DXF_IMPORT_PLUGIN::insertArc( const VECTOR2D& aSegStart, const VECTOR2D& aSegEnd,
                                   double aBulge, double aWidth )
{
    VECTOR2D segment_startpoint( SCALE_FACTOR( aSegStart.x ), SCALE_FACTOR( aSegStart.y ) );
    VECTOR2D segment_endpoint( SCALE_FACTOR( aSegEnd.x ), SCALE_FACTOR( aSegEnd.y ) );

    // ensure aBulge represents an angle from +/- ( 0 .. approx 359.8 deg )
    if( aBulge < -2000.0 )
        aBulge = -2000.0;
    else if( aBulge > 2000.0 )
        aBulge = 2000.0;

    double ang = 4.0 * atan( aBulge );

    // reflect the Y values to put everything in a RHCS
    VECTOR2D sp( aSegStart.x, -aSegStart.y );
    VECTOR2D ep( aSegEnd.x, -aSegEnd.y );

    // angle from end->start
    double offAng = atan2( ep.y - sp.y, ep.x - sp.x );

    // length of subtended segment = 1/2 distance between the 2 points
    double d = 0.5 * sqrt( ( sp.x - ep.x ) * ( sp.x - ep.x ) + ( sp.y - ep.y ) * ( sp.y - ep.y ) );

    // midpoint of the subtended segment
    double xm   = ( sp.x + ep.x ) * 0.5;
    double ym   = ( sp.y + ep.y ) * 0.5;
    double radius = d / sin( ang * 0.5 );

    if( radius < 0.0 )
        radius = -radius;

    // calculate the height of the triangle with base d and hypotenuse r
    double dh2 = radius * radius - d * d;

    // this should only ever happen due to rounding errors when r == d
    if( dh2 < 0.0 )
        dh2 = 0.0;

    double h = sqrt( dh2 );

    if( ang < 0.0 )
        offAng -= M_PI_2;
    else
        offAng += M_PI_2;

    // for angles greater than 180 deg we need to flip the
    // direction in which the arc center is found relative
    // to the midpoint of the subtended segment.
    if( ang < -M_PI )
        offAng += M_PI;
    else if( ang > M_PI )
        offAng -= M_PI;

    // center point
    double cx = h * cos( offAng ) + xm;
    double cy = h * sin( offAng ) + ym;
    VECTOR2D center( SCALE_FACTOR( cx ), SCALE_FACTOR( -cy ) );
    VECTOR2D arc_start;
    EDA_ANGLE angle( ang, RADIANS_T );

    if( ang < 0.0 )
    {
        arc_start = VECTOR2D( SCALE_FACTOR( ep.x ), SCALE_FACTOR( -ep.y ) );
    }
    else
    {
        arc_start = VECTOR2D( SCALE_FACTOR( sp.x ), SCALE_FACTOR( -sp.y ) );
        angle = -angle;
    }

    GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                           : &m_internalImporter;
    bufferToUse->AddArc( center, arc_start, angle, aWidth );

    VECTOR2D radiusDelta( SCALE_FACTOR( radius ), SCALE_FACTOR( radius ) );

    updateImageLimits( center + radiusDelta );
    updateImageLimits( center - radiusDelta );
}


#include "tinysplinecxx.h"

void DXF_IMPORT_PLUGIN::insertSpline( double aWidth )
{
#if 0   // Debug only
    wxLogMessage( "spl deg %d kn %d ctr %d fit %d",
                  m_curr_entity.m_SplineDegree,
                  m_curr_entity.m_SplineKnotsList.size(),
                  m_curr_entity.m_SplineControlPointList.size(),
                  m_curr_entity.m_SplineFitPointList.size() );
#endif

    unsigned imax = m_curr_entity.m_SplineControlPointList.size();

    if( imax < 2 )  // malformed spline
        return;

#if 0   // set to 1 to approximate the spline by segments between 2 control points
    VECTOR2D startpoint( mapX( m_curr_entity.m_SplineControlPointList[0].m_x ),
                         mapY( m_curr_entity.m_SplineControlPointList[0].m_y ) );

    for( unsigned int ii = 1; ii < imax; ++ii )
    {
        VECTOR2D endpoint( mapX( m_curr_entity.m_SplineControlPointList[ii].m_x ),
                           mapY( m_curr_entity.m_SplineControlPointList[ii].m_y ) );

        if( startpoint != endpoint )
        {
            m_internalImporter.AddLine( startpoint, endpoint, aWidth );

            updateImageLimits( startpoint );
            updateImageLimits( endpoint );

            startpoint = endpoint;
        }
    }
#else   // Use bezier curves, supported by pcbnew, to approximate the spline
    std::vector<double> ctrlp;

    for( unsigned ii = 0; ii < imax; ++ii )
    {
        ctrlp.push_back( m_curr_entity.m_SplineControlPointList[ii].m_x );
        ctrlp.push_back( m_curr_entity.m_SplineControlPointList[ii].m_y );
    }

    tinyspline::BSpline beziers;
    std::vector<double> coords;

    try
    {
	    tinyspline::BSpline dxfspline( m_curr_entity.m_SplineControlPointList.size(),
                                       /* coord dim */ 2, m_curr_entity.m_SplineDegree );

	    dxfspline.setControlPoints( ctrlp );
	    dxfspline.setKnots( m_curr_entity.m_SplineKnotsList );

        if( dxfspline.degree() < 3 )
            dxfspline = dxfspline.elevateDegree( 3 - dxfspline.degree() );

        beziers = dxfspline.toBeziers();
        coords = beziers.controlPoints();
    }
    catch( const std::runtime_error& )  // tinyspline throws everything including data validation
                                        // as runtime errors
    {
        // invalid spline definition, drop this block
        ReportMsg( _( "Invalid spline definition encountered" ) );
        return;
    }

    size_t order = beziers.order();
    size_t dim = beziers.dimension();
    size_t numBeziers = ( coords.size() / dim ) / order;

	for( size_t i = 0; i < numBeziers; i++ )
    {
        size_t ii = i * dim * order;
        VECTOR2D start( mapX( coords[ ii ] ), mapY( coords[ ii + 1 ] ) );
        VECTOR2D bezierControl1( mapX( coords[ii + 2] ), mapY( coords[ii + 3] ) );

        // not sure why this happens, but it seems to sometimes slip degree on the final bezier
        VECTOR2D bezierControl2;

        if( ii + 5 >= coords.size() )
            bezierControl2 = bezierControl1;
        else
            bezierControl2 = VECTOR2D( mapX( coords[ii + 4] ), mapY( coords[ii + 5] ) );

        VECTOR2D end;

        if( ii + 7 >= coords.size() )
            end = bezierControl2;
        else
            end = VECTOR2D( mapX( coords[ii + 6] ), mapY( coords[ii + 7] ) );

        GRAPHICS_IMPORTER_BUFFER* bufferToUse = m_currentBlock ? &m_currentBlock->m_buffer
                                                               : &m_internalImporter;
        bufferToUse->AddSpline( start, bezierControl1, bezierControl2, end, aWidth );
    }
#endif
}


void DXF_IMPORT_PLUGIN::updateImageLimits( const VECTOR2D& aPoint )
{
    m_minX = std::min( aPoint.x, m_minX );
    m_maxX = std::max( aPoint.x, m_maxX );

    m_minY = std::min( aPoint.y, m_minY );
    m_maxY = std::max( aPoint.y, m_maxY );
}


MATRIX3x3D DXF_IMPORT_PLUGIN::getArbitraryAxis( DL_Extrusion* aData )
{
    VECTOR3D arbZ, arbX, arbY;

    double direction[3];
    aData->getDirection( direction );

    arbZ = VECTOR3D( direction[0], direction[1], direction[2] ).Normalize();

    if( ( abs( arbZ.x ) < ( 1.0 / 64.0 ) ) && ( abs( arbZ.y ) < ( 1.0 / 64.0 ) ) )
        arbX = VECTOR3D( 0, 1, 0 ).Cross( arbZ ).Normalize();
    else
        arbX = VECTOR3D( 0, 0, 1 ).Cross( arbZ ).Normalize();

    arbY = arbZ.Cross( arbX ).Normalize();

    return MATRIX3x3D{ arbX, arbY, arbZ };
}


VECTOR3D DXF_IMPORT_PLUGIN::wcsToOcs( const MATRIX3x3D& arbitraryAxis, VECTOR3D point )
{
    return arbitraryAxis * point;
}


VECTOR3D DXF_IMPORT_PLUGIN::ocsToWcs( const MATRIX3x3D& arbitraryAxis, VECTOR3D point )
{
    VECTOR3D worldX = wcsToOcs( arbitraryAxis, VECTOR3D( 1, 0, 0 ) );
    VECTOR3D worldY = wcsToOcs( arbitraryAxis, VECTOR3D( 0, 1, 0 ) );
    VECTOR3D worldZ = wcsToOcs( arbitraryAxis, VECTOR3D( 0, 0, 1 ) );

    MATRIX3x3 world( worldX, worldY, worldZ );

    return world * point;
}
