/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * 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
 */

#include <algorithm>                          // for min
#include <bitset>                             // for bitset, operator&, __bi...
#include <math.h>                             // for abs

#include <geometry/seg.h>                     // for SEG
#include <geometry/shape_circle.h>
#include <geometry/shape_line_chain.h>        // for SHAPE_LINE_CHAIN
#include <geometry/shape_poly_set.h>          // for SHAPE_POLY_SET, SHAPE_P...
#include <geometry/shape_segment.h>
#include <string_utils.h>
#include <macros.h>
#include <math/util.h>                        // for KiROUND
#include <math/vector2d.h>                    // for VECTOR2I
#include <plotters/plotter_gerber.h>
#include <trigo.h>
#include <font/stroke_font.h>
#include <gal/gal_display_options.h>
#include <callback_gal.h>
#include <core/typeinfo.h>                    // for dyn_cast, PCB_DIMENSION_T
#include <gbr_metadata.h>
#include <gbr_netlist_metadata.h>             // for GBR_NETLIST_METADATA
#include <layer_ids.h>                        // for LSET, IsCopperLayer
#include <lset.h>
#include <pcbplot.h>
#include <pcb_plot_params.h>                  // for PCB_PLOT_PARAMS, PCB_PL...
#include <advanced_config.h>

#include <pcb_dimension.h>
#include <pcb_shape.h>
#include <footprint.h>
#include <pcb_track.h>
#include <pad.h>
#include <pcb_target.h>
#include <pcb_text.h>
#include <pcb_textbox.h>
#include <pcb_tablecell.h>
#include <pcb_table.h>
#include <zone.h>

#include <wx/debug.h>                         // for wxASSERT_MSG


COLOR4D BRDITEMS_PLOTTER::getColor( int aLayer ) const
{
    COLOR4D color = ColorSettings()->GetColor( aLayer );

    // A hack to avoid plotting a white item in white color on white paper
    if( color == COLOR4D::WHITE )
        color = COLOR4D( LIGHTGRAY );

    return color;
}


void BRDITEMS_PLOTTER::PlotPadNumber( const PAD* aPad, const COLOR4D& aColor )
{
    wxString padNumber = UnescapeString( aPad->GetNumber() );

    if( padNumber.IsEmpty() )
        return;

    BOX2I    padBBox = aPad->GetBoundingBox();
    VECTOR2I position = padBBox.Centre();
    VECTOR2I padsize = padBBox.GetSize();

    // TODO(JE) padstacks
    if( aPad->GetShape( PADSTACK::ALL_LAYERS ) == PAD_SHAPE::CUSTOM )
    {
        // See if we have a number box
        for( const std::shared_ptr<PCB_SHAPE>& primitive : aPad->GetPrimitives( PADSTACK::ALL_LAYERS ) )
        {
            if( primitive->IsProxyItem() && primitive->GetShape() == SHAPE_T::RECTANGLE )
            {
                position = primitive->GetCenter();
                RotatePoint( position, aPad->GetOrientation() );
                position += aPad->ShapePos( PADSTACK::ALL_LAYERS );

                padsize.x = abs( primitive->GetBotRight().x - primitive->GetTopLeft().x );
                padsize.y = abs( primitive->GetBotRight().y - primitive->GetTopLeft().y );

                break;
            }
        }
    }

    if( aPad->GetShape( PADSTACK::ALL_LAYERS ) != PAD_SHAPE::CUSTOM )
    {
        // Don't allow a 45° rotation to bloat a pad's bounding box unnecessarily
        int limit = KiROUND( std::min( aPad->GetSize( PADSTACK::ALL_LAYERS ).x,
                                       aPad->GetSize( PADSTACK::ALL_LAYERS ).y ) * 1.1 );

        if( padsize.x > limit && padsize.y > limit )
        {
            padsize.x = limit;
            padsize.y = limit;
        }
    }

    TEXT_ATTRIBUTES textAttrs;

    textAttrs.m_Mirrored = m_plotter->GetPlotMirrored();

    if( padsize.x < ( padsize.y * 0.95 ) )
    {
        textAttrs.m_Angle = ANGLE_90;
        std::swap( padsize.x, padsize.y );
    }

    // approximate the size of the pad number text:
    // We use a size for at least 3 chars, to give a good look even for short numbers
    int tsize = KiROUND( padsize.x / std::max( PrintableCharCount( padNumber ), 3 ) );
    tsize = std::min( tsize, padsize.y );

    // enforce a max size
    tsize = std::min( tsize, pcbIUScale.mmToIU( 5.0 ) );

    textAttrs.m_Size = VECTOR2I( tsize, tsize );

    // use a somewhat spindly font to go with the outlined pads
    textAttrs.m_StrokeWidth = KiROUND( tsize / 12.0 );

    m_plotter->PlotText( position, aColor, padNumber, textAttrs );
}


void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, PCB_LAYER_ID aLayer, const COLOR4D& aColor,
                                OUTLINE_MODE aPlotMode )
{
    VECTOR2I     shape_pos = aPad->ShapePos( aLayer );
    GBR_METADATA metadata;

    bool plotOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
    bool plotOnExternalCopperLayer = ( m_layerMask & LSET::ExternalCuMask() ).any();

    // Pad not on the solder mask layer cannot be soldered.
    // therefore it can have a specific aperture attribute.
    // Not yet in use.
    // bool isPadOnBoardTechLayers = ( aPad->GetLayerSet() & LSET::AllBoardTechMask() ).any();

    metadata.SetCmpReference( aPad->GetParentFootprint()->GetReference() );

    if( plotOnCopperLayer )
    {
        metadata.SetNetAttribType( GBR_NETINFO_ALL );
        metadata.SetCopper( true );

        // Gives a default attribute, for instance for pads used as tracks in net ties:
        // Connector pads and SMD pads are on external layers
        // if on internal layers, they are certainly used as net tie
        // and are similar to tracks: just conductor items
        metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );

        const bool useUTF8 = false;
        const bool useQuoting = false;
        metadata.SetPadName( aPad->GetNumber(), useUTF8, useQuoting );

        if( !aPad->GetNumber().IsEmpty() )
            metadata.SetPadPinFunction( aPad->GetPinFunction(), useUTF8, useQuoting );

        metadata.SetNetName( aPad->GetNetname() );

        // Some pads are mechanical pads ( through hole or smd )
        // when this is the case, they have no pad name and/or are not plated.
        // In this case gerber files have slightly different attributes.
        if( aPad->GetAttribute() == PAD_ATTRIB::NPTH || aPad->GetNumber().IsEmpty() )
            metadata.m_NetlistMetadata.m_NotInNet = true;

        if( !plotOnExternalCopperLayer )
        {
            // the .P object attribute (GBR_NETLIST_METADATA::GBR_NETINFO_PAD)
            // is used on outer layers, unless the component is embedded
            // or a "etched" component (fp only drawn, not a physical component)
            // Currently, Pcbnew does not handle embedded component, so we disable the .P
            // attribute on internal layers
            // Note the Gerber doc is not really clear about through holes pads about the .P
            metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET |
                                           GBR_NETLIST_METADATA::GBR_NETINFO_CMP );

        }

        // Some attributes are reserved to the external copper layers:
        // GBR_APERTURE_ATTRIB_CONNECTORPAD and GBR_APERTURE_ATTRIB_SMDPAD_CUDEF
        // for instance.
        // Pad with type PAD_ATTRIB::CONN or PAD_ATTRIB::SMD that is not on outer layer
        // has its aperture attribute set to GBR_APERTURE_ATTRIB_CONDUCTOR
        switch( aPad->GetAttribute() )
        {
        case PAD_ATTRIB::NPTH:       // Mechanical pad through hole
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
            break;

        case PAD_ATTRIB::PTH :       // Pad through hole, a hole is also expected
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_COMPONENTPAD );
            break;

        case PAD_ATTRIB::CONN:       // Connector pads, no solder paste but with solder mask.
            if( plotOnExternalCopperLayer )
                metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONNECTORPAD );
            break;

        case PAD_ATTRIB::SMD:        // SMD pads (on external copper layer only)
                                     // with solder paste and mask
            if( plotOnExternalCopperLayer )
                metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_SMDPAD_CUDEF );
            break;
        }

        // Fabrication properties can have specific GBR_APERTURE_METADATA options
        // that replace previous aperture attribute:
        switch( aPad->GetProperty() )
        {
        case PAD_PROP::BGA:          // Only applicable to outer layers
            if( plotOnExternalCopperLayer )
                metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_BGAPAD_CUDEF );
            break;

        case PAD_PROP::FIDUCIAL_GLBL:
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_GLBL );
            break;

        case PAD_PROP::FIDUCIAL_LOCAL:
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_LOCAL );
            break;

        case PAD_PROP::TESTPOINT:    // Only applicable to outer layers
            if( plotOnExternalCopperLayer )
                metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_TESTPOINT );
            break;

        case PAD_PROP::HEATSINK:
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_HEATSINKPAD );
            break;

        case PAD_PROP::CASTELLATED:
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CASTELLATEDPAD );
            break;

        case PAD_PROP::NONE:
        case PAD_PROP::MECHANICAL:
            break;
        }

        // Ensure NPTH pads have *always* the GBR_APERTURE_ATTRIB_WASHERPAD attribute
        if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
            metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
    }
    else
    {
        metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
    }

    // Set plot color (change WHITE to LIGHTGRAY because
    // the white items are not seen on a white paper or screen
    m_plotter->SetColor( aColor != WHITE ? aColor : LIGHTGRAY );

    if( aPlotMode == SKETCH )
        m_plotter->SetCurrentLineWidth( GetSketchPadLineWidth(), &metadata );

    switch( aPad->GetShape( aLayer ) )
    {
    case PAD_SHAPE::CIRCLE:
        m_plotter->FlashPadCircle( shape_pos, aPad->GetSize( aLayer ).x,
                                   aPlotMode, &metadata );
        break;

    case PAD_SHAPE::OVAL:
        m_plotter->FlashPadOval( shape_pos, aPad->GetSize( aLayer ),
                                 aPad->GetOrientation(), aPlotMode, &metadata );
        break;

    case PAD_SHAPE::RECTANGLE:
        m_plotter->FlashPadRect( shape_pos, aPad->GetSize( aLayer ),
                                 aPad->GetOrientation(), aPlotMode, &metadata );
        break;

    case PAD_SHAPE::ROUNDRECT:
        m_plotter->FlashPadRoundRect( shape_pos, aPad->GetSize( aLayer ),
                                      aPad->GetRoundRectCornerRadius( aLayer ),
                                      aPad->GetOrientation(), aPlotMode, &metadata );
        break;

    case PAD_SHAPE::TRAPEZOID:
    {
        // Build the pad polygon in coordinates relative to the pad
        // (i.e. for a pad at pos 0,0, rot 0.0). Needed to use aperture macros,
        // to be able to create a pattern common to all trapezoid pads having the same shape
        VECTOR2I coord[4];

        // Order is lower left, lower right, upper right, upper left.
        VECTOR2I half_size = aPad->GetSize( aLayer ) / 2;
        VECTOR2I trap_delta = aPad->GetDelta( aLayer ) / 2;

        coord[0] = VECTOR2I( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
        coord[1] = VECTOR2I( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
        coord[2] = VECTOR2I( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
        coord[3] = VECTOR2I( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );

        m_plotter->FlashPadTrapez( shape_pos, coord, aPad->GetOrientation(), aPlotMode, &metadata );
    }
        break;

    case PAD_SHAPE::CHAMFERED_RECT:
        if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
        {
            GERBER_PLOTTER* gerberPlotter = static_cast<GERBER_PLOTTER*>( m_plotter );

            gerberPlotter->FlashPadChamferRoundRect( shape_pos,
                    aPad->GetSize( aLayer ),
                    aPad->GetRoundRectCornerRadius( aLayer ),
                    aPad->GetChamferRectRatio( aLayer ),
                    aPad->GetChamferPositions( aLayer ), aPad->GetOrientation(),
                    aPlotMode, &metadata );
            break;
        }

        KI_FALLTHROUGH;

    default:
    case PAD_SHAPE::CUSTOM:
    {
        const std::shared_ptr<SHAPE_POLY_SET>& polygons =
            aPad->GetEffectivePolygon( aLayer, ERROR_INSIDE );

        if( polygons->OutlineCount() )
        {
            m_plotter->FlashPadCustom( shape_pos, aPad->GetSize( aLayer ), aPad->GetOrientation(),
                                       polygons.get(), aPlotMode, &metadata );
        }
    }
        break;
    }
}


void BRDITEMS_PLOTTER::PlotFootprintTextItems( const FOOTPRINT* aFootprint )
{
    if( !GetPlotFPText() )
        return;

    const PCB_TEXT* reference = &aFootprint->Reference();
    PCB_LAYER_ID    refLayer = reference->GetLayer();

    // Reference and value have special controls for forcing their plotting
    if( GetPlotReference()
            && m_layerMask[refLayer]
            && reference->IsVisible()
            && !( aFootprint->IsDNP() && hideDNPItems( refLayer ) ) )
    {
        PlotText( reference, refLayer, reference->IsKnockout(), reference->GetFontMetrics(),
                  aFootprint->IsDNP() && crossoutDNPItems( refLayer ) );
    }

    const PCB_TEXT* value  = &aFootprint->Value();
    PCB_LAYER_ID    valueLayer = value->GetLayer();

    if( GetPlotValue()
            && m_layerMask[valueLayer]
            && value->IsVisible()
            && !( aFootprint->IsDNP() && hideDNPItems( valueLayer ) ) )
    {
        PlotText( value, valueLayer, value->IsKnockout(), value->GetFontMetrics(),
                  false );
    }

    std::vector<PCB_TEXT*> texts;

    // Skip the reference and value texts that are handled specially
    for( PCB_FIELD* field : aFootprint->GetFields() )
    {
        if( field->IsReference() || field->IsValue() )
            continue;

        texts.push_back( field );
    }

    for( BOARD_ITEM* item : aFootprint->GraphicalItems() )
    {
        if( PCB_TEXT* textItem = dynamic_cast<PCB_TEXT*>( item ) )
            texts.push_back( textItem );
    }

    for( const PCB_TEXT* text : texts )
    {
        if( !text->IsVisible() )
            continue;

        PCB_LAYER_ID textLayer = text->GetLayer();
        bool         strikeout = false;

        if( textLayer == Edge_Cuts || textLayer >= PCB_LAYER_ID_COUNT )
            continue;

        if( aFootprint->IsDNP() && hideDNPItems( textLayer ) )
            continue;

        if( !m_layerMask[textLayer] || aFootprint->GetPrivateLayers().test( textLayer ) )
            continue;

        if( text->GetText() == wxT( "${REFERENCE}" ) )
        {
            if( !GetPlotReference() )
                continue;

            strikeout = aFootprint->IsDNP() && crossoutDNPItems( textLayer );
        }

        if( text->GetText() == wxT( "${VALUE}" ) )
        {
            if( !GetPlotValue() )
                continue;
        }

        PlotText( text, textLayer, text->IsKnockout(), text->GetFontMetrics(), strikeout );
    }
}


void BRDITEMS_PLOTTER::PlotBoardGraphicItem( const BOARD_ITEM* item )
{
    switch( item->Type() )
    {
    case PCB_SHAPE_T:
        PlotShape( static_cast<const PCB_SHAPE*>( item ) );
        break;

    case PCB_TEXT_T:
    {
        const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item );
        PlotText( text, text->GetLayer(), text->IsKnockout(), text->GetFontMetrics() );
        break;
    }

    case PCB_TEXTBOX_T:
    {
        m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );

        const PCB_TEXTBOX* textbox = static_cast<const PCB_TEXTBOX*>( item );
        PlotText( textbox, textbox->GetLayer(), textbox->IsKnockout(), textbox->GetFontMetrics() );

        if( textbox->IsBorderEnabled() )
            PlotShape( textbox );

        m_plotter->SetTextMode( GetTextMode() );
        break;
    }

    case PCB_TABLE_T:
    {
        const PCB_TABLE* table = static_cast<const PCB_TABLE*>( item );

        m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );

        for( const PCB_TABLECELL* cell : table->GetCells() )
            PlotText( cell, cell->GetLayer(), cell->IsKnockout(), cell->GetFontMetrics() );

        PlotTableBorders( table );

        m_plotter->SetTextMode( GetTextMode() );
        break;
    }

    case PCB_DIM_ALIGNED_T:
    case PCB_DIM_CENTER_T:
    case PCB_DIM_RADIAL_T:
    case PCB_DIM_ORTHOGONAL_T:
    case PCB_DIM_LEADER_T:
        m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );

        PlotDimension( static_cast<const PCB_DIMENSION_BASE*>( item ) );

        m_plotter->SetTextMode( GetTextMode() );
        break;

    case PCB_TARGET_T:
        PlotPcbTarget( static_cast<const PCB_TARGET*>( item ) );
        break;

    default:
        break;
    }
}


void BRDITEMS_PLOTTER::PlotDimension( const PCB_DIMENSION_BASE* aDim )
{
    if( !m_layerMask[aDim->GetLayer()] )
        return;

    COLOR4D color = ColorSettings()->GetColor( aDim->GetLayer() );

    // Set plot color (change WHITE to LIGHTGRAY because
    // the white items are not seen on a white paper or screen
    m_plotter->SetColor( color != WHITE ? color : LIGHTGRAY);

    PlotText( aDim, aDim->GetLayer(), false, aDim->GetFontMetrics() );

    PCB_SHAPE temp_item;

    temp_item.SetStroke( STROKE_PARAMS( aDim->GetLineThickness(), LINE_STYLE::SOLID ) );
    temp_item.SetLayer( aDim->GetLayer() );

    for( const std::shared_ptr<SHAPE>& shape : aDim->GetShapes() )
    {
        switch( shape->Type() )
        {
        case SH_SEGMENT:
        {
            const SEG& seg = static_cast<const SHAPE_SEGMENT*>( shape.get() )->GetSeg();

            temp_item.SetShape( SHAPE_T::SEGMENT );
            temp_item.SetStart( seg.A );
            temp_item.SetEnd( seg.B );

            PlotShape( &temp_item );
            break;
        }

        case SH_CIRCLE:
        {
            VECTOR2I start( shape->Centre() );
            int radius = static_cast<const SHAPE_CIRCLE*>( shape.get() )->GetRadius();

            temp_item.SetShape( SHAPE_T::CIRCLE );
            temp_item.SetFilled( false );
            temp_item.SetStart( start );
            temp_item.SetEnd( VECTOR2I( start.x + radius, start.y ) );

            PlotShape( &temp_item );
            break;
        }

        default:
            break;
        }
    }
}


void BRDITEMS_PLOTTER::PlotPcbTarget( const PCB_TARGET* aMire )
{
    int dx1, dx2, dy1, dy2, radius;

    if( !m_layerMask[aMire->GetLayer()] )
        return;

    m_plotter->SetColor( getColor( aMire->GetLayer() ) );

    PCB_SHAPE temp_item;

    temp_item.SetShape( SHAPE_T::CIRCLE );
    temp_item.SetFilled( false );
    temp_item.SetStroke( STROKE_PARAMS( aMire->GetWidth(), LINE_STYLE::SOLID ) );
    temp_item.SetLayer( aMire->GetLayer() );
    temp_item.SetStart( aMire->GetPosition() );
    radius = aMire->GetSize() / 3;

    if( aMire->GetShape() )   // temp_item X
        radius = aMire->GetSize() / 2;

    // Draw the circle
    temp_item.SetEnd( VECTOR2I( temp_item.GetStart().x + radius, temp_item.GetStart().y ) );

    PlotShape( &temp_item );

    temp_item.SetShape( SHAPE_T::SEGMENT );

    radius = aMire->GetSize() / 2;
    dx1    = radius;
    dy1    = 0;
    dx2    = 0;
    dy2    = radius;

    if( aMire->GetShape() )    // Shape X
    {
        dx1 = dy1 = radius;
        dx2 = dx1;
        dy2 = -dy1;
    }

    VECTOR2I mirePos( aMire->GetPosition() );

    // Draw the X or + temp_item:
    temp_item.SetStart( VECTOR2I( mirePos.x - dx1, mirePos.y - dy1 ) );
    temp_item.SetEnd( VECTOR2I( mirePos.x + dx1, mirePos.y + dy1 ) );
    PlotShape( &temp_item );

    temp_item.SetStart( VECTOR2I( mirePos.x - dx2, mirePos.y - dy2 ) );
    temp_item.SetEnd( VECTOR2I( mirePos.x + dx2, mirePos.y + dy2 ) );
    PlotShape( &temp_item );
}


void BRDITEMS_PLOTTER::PlotFootprintGraphicItems( const FOOTPRINT* aFootprint )
{
    for( const BOARD_ITEM* item : aFootprint->GraphicalItems() )
    {
        PCB_LAYER_ID itemLayer = item->GetLayer();

        if( aFootprint->GetPrivateLayers().test( itemLayer ) )
            continue;

        if( aFootprint->IsDNP() && hideDNPItems( itemLayer ) )
            continue;

        if( !( m_layerMask & item->GetLayerSet() ).any() )
            continue;

        switch( item->Type() )
        {
        case PCB_SHAPE_T:
            PlotShape( static_cast<const PCB_SHAPE*>( item ) );
            break;

        case PCB_TEXTBOX_T:
        {
            const PCB_TEXTBOX* textbox = static_cast<const PCB_TEXTBOX*>( item );

            m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );

            PlotText( textbox, textbox->GetLayer(), textbox->IsKnockout(),
                      textbox->GetFontMetrics() );

            if( textbox->IsBorderEnabled() )
                PlotShape( textbox );

            m_plotter->SetTextMode( GetTextMode() );
            break;
        }

        case PCB_TABLE_T:
        {
            const PCB_TABLE* table = static_cast<const PCB_TABLE*>( item );

            m_plotter->SetTextMode( PLOT_TEXT_MODE::STROKE );

            for( const PCB_TABLECELL* cell : table->GetCells() )
                PlotText( cell, cell->GetLayer(), cell->IsKnockout(), cell->GetFontMetrics() );

            PlotTableBorders( table );

            m_plotter->SetTextMode( GetTextMode() );
            break;
        }

        case PCB_DIM_ALIGNED_T:
        case PCB_DIM_CENTER_T:
        case PCB_DIM_RADIAL_T:
        case PCB_DIM_ORTHOGONAL_T:
        case PCB_DIM_LEADER_T:
            PlotDimension( static_cast<const PCB_DIMENSION_BASE*>( item ) );
            break;

        case PCB_TEXT_T:
            // Plotted in PlotFootprintTextItems()
            break;

        case PCB_REFERENCE_IMAGE_T:
            // Not plotted at all
            break;

        default:
            UNIMPLEMENTED_FOR( item->GetClass() );
        }
    }
}


void BRDITEMS_PLOTTER::PlotText( const EDA_TEXT* aText, PCB_LAYER_ID aLayer, bool aIsKnockout,
                                 const KIFONT::METRICS& aFontMetrics, bool aStrikeout )
{
    KIFONT::FONT* font = aText->GetDrawFont( m_plotter->RenderSettings() );
    wxString      shownText( aText->GetShownText( true ) );

    if( shownText.IsEmpty() )
        return;

    if( !m_layerMask[aLayer] )
        return;

    GBR_METADATA gbr_metadata;

    if( IsCopperLayer( aLayer ) )
        gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );

    COLOR4D color = getColor( aLayer );
    m_plotter->SetColor( color );

    VECTOR2I pos = aText->GetTextPos();

    TEXT_ATTRIBUTES attrs = aText->GetAttributes();
    attrs.m_StrokeWidth = aText->GetEffectiveTextPenWidth();
    attrs.m_Angle = aText->GetDrawRotation();
    attrs.m_Multiline = false;

    m_plotter->SetCurrentLineWidth( attrs.m_StrokeWidth );

    auto strikeoutText =
            [&]( const PCB_TEXT* text )
            {
                SHAPE_POLY_SET  textPoly;

                text->TransformTextToPolySet( textPoly, 0, ARC_LOW_DEF, ERROR_INSIDE );
                textPoly.Rotate( -text->GetDrawRotation(), text->GetDrawPos() );

                BOX2I    rect = textPoly.BBox();
                VECTOR2I start( rect.GetLeft() - attrs.m_StrokeWidth,
                                ( rect.GetTop() + rect.GetBottom() ) / 2 );
                VECTOR2I end( rect.GetRight() + attrs.m_StrokeWidth,
                              ( rect.GetTop() + rect.GetBottom() ) / 2 );

                RotatePoint( start, text->GetDrawPos(), text->GetDrawRotation() );
                RotatePoint( end, text->GetDrawPos(), text->GetDrawRotation() );

                m_plotter->ThickSegment( start, end, attrs.m_StrokeWidth, FILLED, nullptr );
            };

    if( aIsKnockout )
    {
        const PCB_TEXT* text = static_cast<const PCB_TEXT*>( aText );
        SHAPE_POLY_SET  finalPoly;

        text->TransformTextToPolySet( finalPoly, 0, m_board->GetDesignSettings().m_MaxError,
                                      ERROR_INSIDE );
        finalPoly.Fracture();

        for( int ii = 0; ii < finalPoly.OutlineCount(); ++ii )
            m_plotter->PlotPoly( finalPoly.Outline( ii ), FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
    }
    else
    {
        if( font->IsOutline() && !m_board->GetEmbeddedFiles()->GetAreFontsEmbedded() )
        {
            KIGFX::GAL_DISPLAY_OPTIONS empty_opts;

            CALLBACK_GAL callback_gal( empty_opts,
                    // Stroke callback
                    [&]( const VECTOR2I& aPt1, const VECTOR2I& aPt2 )
                    {
                        m_plotter->ThickSegment( aPt1, aPt2, attrs.m_StrokeWidth, FILLED, nullptr );
                    },
                    // Polygon callback
                    [&]( const SHAPE_LINE_CHAIN& aPoly )
                    {
                        m_plotter->PlotPoly( aPoly, FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
                    } );

            callback_gal.DrawGlyphs( *aText->GetRenderCache( font, shownText ) );
        }
        else if( aText->IsMultilineAllowed() )
        {
            std::vector<VECTOR2I> positions;
            wxArrayString strings_list;
            wxStringSplit( shownText, strings_list, '\n' );
            positions.reserve(  strings_list.Count() );

            aText->GetLinePositions( m_plotter->RenderSettings(), positions, (int) strings_list.Count() );

            for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
            {
                wxString& txt =  strings_list.Item( ii );
                m_plotter->PlotText( positions[ii], color, txt, attrs, font, aFontMetrics,
                                     &gbr_metadata );
            }

            if( aStrikeout && strings_list.Count() == 1 )
                strikeoutText( static_cast<const PCB_TEXT*>( aText ) );
        }
        else
        {
            m_plotter->PlotText( pos, color, shownText, attrs, font, aFontMetrics, &gbr_metadata );

            if( aStrikeout )
                strikeoutText( static_cast<const PCB_TEXT*>( aText ) );
        }
    }
}


void BRDITEMS_PLOTTER::PlotZone( const ZONE* aZone, PCB_LAYER_ID aLayer,
                                 const SHAPE_POLY_SET& aPolysList )
{
    if( aPolysList.IsEmpty() )
        return;

    GBR_METADATA gbr_metadata;

    if( aZone->IsOnCopperLayer() )
    {
        gbr_metadata.SetNetName( aZone->GetNetname() );
        gbr_metadata.SetCopper( true );

        // Zones with no net name can exist.
        // they are not used to connect items, so the aperture attribute cannot
        // be set as conductor
        if( aZone->GetNetname().IsEmpty() )
        {
            gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
        }
        else
        {
            gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
            gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
        }
    }

    m_plotter->SetColor( getColor( aLayer ) );

    m_plotter->StartBlock( nullptr );    // Clean current object attributes

    /*
     * In non filled mode the outline is plotted, but not the filling items
     */

    for( int idx = 0; idx < aPolysList.OutlineCount(); ++idx )
    {
        const SHAPE_LINE_CHAIN& outline = aPolysList.Outline( idx );

        // Plot the current filled area (as region for Gerber plotter to manage attributes)
        if( GetPlotMode() == FILLED )
        {
            if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
            {
                static_cast<GERBER_PLOTTER*>( m_plotter )->PlotGerberRegion( outline,
                                                                             &gbr_metadata );
            }
            else
            {
                m_plotter->PlotPoly( outline, FILL_T::FILLED_SHAPE, 0, &gbr_metadata );
            }
        }
        else
        {
            m_plotter->SetCurrentLineWidth( -1 );
        }
    }

    m_plotter->EndBlock( nullptr );    // Clear object attributes
}


void BRDITEMS_PLOTTER::PlotShape( const PCB_SHAPE* aShape )
{
    if( !( m_layerMask & aShape->GetLayerSet() ).any() )
        return;

    OUTLINE_MODE  plotMode = GetPlotMode();
    int          thickness = aShape->GetWidth();
    int             margin = thickness; // unclamped thickness (can be negative)
    LINE_STYLE   lineStyle = aShape->GetStroke().GetLineStyle();
    bool     onCopperLayer = ( LSET::AllCuMask() & m_layerMask ).any();
    bool onSolderMaskLayer = ( LSET( { F_Mask, B_Mask } ) & m_layerMask ).any();

    if( onSolderMaskLayer
        && aShape->HasSolderMask()
        && IsExternalCopperLayer( aShape->GetLayer() ) )
    {
        margin   += 2 * aShape->GetSolderMaskExpansion();
        thickness = std::max( margin, 0 );
    }

    m_plotter->SetColor( getColor( aShape->GetLayer() ) );

    const FOOTPRINT* parentFP = aShape->GetParentFootprint();
    GBR_METADATA     gbr_metadata;

    if( parentFP )
    {
        gbr_metadata.SetCmpReference( parentFP->GetReference() );
        gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
    }

    if( parentFP && parentFP->IsDNP() && GetSketchDNPFPsOnFabLayers() )
    {
        if( aShape->GetLayer() == F_Fab || aShape->GetLayer() == B_Fab )
            plotMode = SKETCH;
    }

    if( aShape->GetLayer() == Edge_Cuts )
    {
        gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT );
    }
    else if( onCopperLayer )
    {
        if( parentFP )
        {
            gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_ETCHEDCMP );
            gbr_metadata.SetCopper( true );
        }
        else if( aShape->GetNetCode() > 0 )
        {
            gbr_metadata.SetCopper( true );
            gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
            gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
            gbr_metadata.SetNetName( aShape->GetNetname() );
        }
        else
        {
            // Graphic items (PCB_SHAPE, TEXT) having no net have the NonConductor attribute
            // Graphic items having a net have the Conductor attribute, but are not (yet?)
            // supported in Pcbnew
            gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
        }
    }

    if( lineStyle <= LINE_STYLE::FIRST_TYPE )
    {
        switch( aShape->GetShape() )
        {
        case SHAPE_T::SEGMENT:
            m_plotter->ThickSegment( aShape->GetStart(), aShape->GetEnd(), thickness, plotMode,
                                     &gbr_metadata );
            break;

        case SHAPE_T::CIRCLE:
            if( aShape->IsFilled() )
            {
                int diameter = aShape->GetRadius() * 2 + thickness;

                if( margin < 0 )
                {
                    diameter += margin;
                    diameter = std::max( diameter, 0 );
                }

                m_plotter->FilledCircle( aShape->GetStart(), diameter, plotMode, &gbr_metadata );
            }
            else
            {
                m_plotter->ThickCircle( aShape->GetStart(), aShape->GetRadius() * 2, thickness,
                                        plotMode, &gbr_metadata );
            }

            break;

        case SHAPE_T::ARC:
        {
            // when startAngle == endAngle ThickArc() doesn't know whether it's 0 deg and 360 deg
            // but it is a circle
            if( std::abs( aShape->GetArcAngle().AsDegrees() ) == 360.0 )
            {
                m_plotter->ThickCircle( aShape->GetCenter(), aShape->GetRadius() * 2, thickness,
                                        plotMode, &gbr_metadata );
            }
            else
            {
                m_plotter->ThickArc( *aShape, plotMode, &gbr_metadata, thickness );
            }

            break;
        }

        case SHAPE_T::BEZIER:
            m_plotter->BezierCurve( aShape->GetStart(), aShape->GetBezierC1(),
                                    aShape->GetBezierC2(), aShape->GetEnd(), 0, thickness );
            break;

        case SHAPE_T::POLY:
            if( aShape->IsPolyShapeValid() )
            {
                if( plotMode == SKETCH )
                {
                    for( auto it = aShape->GetPolyShape().CIterateSegments( 0 ); it; it++ )
                    {
                        const SEG& seg = it.Get();
                        m_plotter->ThickSegment( seg.A, seg.B, thickness, SKETCH, &gbr_metadata );
                    }
                }
                else
                {
                    m_plotter->SetCurrentLineWidth( thickness, &gbr_metadata );

                    // Draw the polygon: only one polygon is expected
                    // However we provide a multi polygon shape drawing
                    // ( for the future or to show a non expected shape )
                    // This must be simplified and fractured to prevent overlapping polygons
                    // from generating invalid Gerber files
                    SHAPE_POLY_SET tmpPoly = aShape->GetPolyShape().CloneDropTriangulation();
                    tmpPoly.Fracture();

                    if( margin < 0 )
                    {
                        tmpPoly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS,
                                         m_board->GetDesignSettings().m_MaxError );
                    }

                    FILL_T fill = aShape->IsFilled() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL;

                    for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj )
                    {
                        SHAPE_LINE_CHAIN& poly = tmpPoly.Outline( jj );

                        // Ensure the polygon is closed:
                        poly.SetClosed( true );

                        // Plot the current filled area
                        // (as region for Gerber plotter to manage attributes)
                        if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
                        {
                            GERBER_PLOTTER* gbr_plotter = static_cast<GERBER_PLOTTER*>( m_plotter );
                            gbr_plotter->PlotPolyAsRegion( poly, fill, thickness, &gbr_metadata );
                        }
                        else
                        {
                            m_plotter->PlotPoly( poly, fill, thickness, &gbr_metadata );
                        }
                    }
                }
            }

            break;

        case SHAPE_T::RECTANGLE:
        {
            std::vector<VECTOR2I> pts = aShape->GetRectCorners();

            if( plotMode == SKETCH )
            {
                m_plotter->ThickSegment( pts[0], pts[1], thickness, SKETCH, &gbr_metadata );
                m_plotter->ThickSegment( pts[1], pts[2], thickness, SKETCH, &gbr_metadata );
                m_plotter->ThickSegment( pts[2], pts[3], thickness, SKETCH, &gbr_metadata );
                m_plotter->ThickSegment( pts[3], pts[0], thickness, SKETCH, &gbr_metadata );
            }
            else
            {
                SHAPE_POLY_SET poly;
                poly.NewOutline();

                for( const VECTOR2I& pt : pts )
                    poly.Append( pt );

                if( margin < 0 )
                {
                    poly.Inflate( margin / 2, CORNER_STRATEGY::ROUND_ALL_CORNERS,
                                  m_board->GetDesignSettings().m_MaxError );
                }

                FILL_T fill_mode = aShape->IsFilled() ? FILL_T::FILLED_SHAPE : FILL_T::NO_FILL;

                if( poly.OutlineCount() > 0 )
                {
                    if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
                    {
                        GERBER_PLOTTER* gbr_plotter = static_cast<GERBER_PLOTTER*>( m_plotter );
                        gbr_plotter->PlotPolyAsRegion( poly.COutline( 0 ), fill_mode, thickness,
                                                       &gbr_metadata );
                    }
                    else
                    {
                        m_plotter->PlotPoly( poly.COutline( 0 ), fill_mode, thickness,
                                             &gbr_metadata );
                    }
                }
            }

            break;
        }

        default:
            UNIMPLEMENTED_FOR( aShape->SHAPE_T_asString() );
        }
    }
    else
    {
        std::vector<SHAPE*> shapes = aShape->MakeEffectiveShapes( true );

        for( SHAPE* shape : shapes )
        {
            STROKE_PARAMS::Stroke( shape, lineStyle, aShape->GetWidth(),
                                   m_plotter->RenderSettings(),
                                   [&]( const VECTOR2I& a, const VECTOR2I& b )
                                   {
                                       m_plotter->ThickSegment( a, b, thickness, plotMode,
                                                                &gbr_metadata );
                                   } );
        }

        for( SHAPE* shape : shapes )
            delete shape;
    }
}


void BRDITEMS_PLOTTER::PlotTableBorders( const PCB_TABLE* aTable )
{
    if( !m_layerMask[aTable->GetLayer()] )
        return;

    GBR_METADATA gbr_metadata;

    if( const FOOTPRINT* parentFP = aTable->GetParentFootprint() )
    {
        gbr_metadata.SetCmpReference( parentFP->GetReference() );
        gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
    }

    aTable->DrawBorders(
            [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
            {
                int        lineWidth = stroke.GetWidth();
                LINE_STYLE lineStyle = stroke.GetLineStyle();

                if( lineStyle <= LINE_STYLE::FIRST_TYPE )
                {
                    m_plotter->ThickSegment( ptA, ptB, lineWidth, GetPlotMode(), &gbr_metadata );
                }
                else
                {
                    SHAPE_SEGMENT seg( ptA, ptB );

                    STROKE_PARAMS::Stroke( &seg, lineStyle, lineWidth, m_plotter->RenderSettings(),
                            [&]( const VECTOR2I& a, const VECTOR2I& b )
                            {
                                m_plotter->ThickSegment( a, b, lineWidth, GetPlotMode(),
                                                         &gbr_metadata );
                            } );
                }
            } );
}


void BRDITEMS_PLOTTER::plotOneDrillMark( PAD_DRILL_SHAPE aDrillShape, const VECTOR2I& aDrillPos,
                                         const VECTOR2I& aDrillSize, const VECTOR2I& aPadSize,
                                         const EDA_ANGLE& aOrientation, int aSmallDrill )
{
    VECTOR2I drillSize = aDrillSize;

    // Small drill marks have no significance when applied to slots
    if( aSmallDrill && aDrillShape == PAD_DRILL_SHAPE::CIRCLE )
        drillSize.x = std::min( aSmallDrill, drillSize.x );

    // Round holes only have x diameter, slots have both
    drillSize.x -= getFineWidthAdj();
    drillSize.x = std::clamp( drillSize.x, 1, aPadSize.x - 1 );

    if( aDrillShape == PAD_DRILL_SHAPE::OBLONG )
    {
        drillSize.y -= getFineWidthAdj();
        drillSize.y = std::clamp( drillSize.y, 1, aPadSize.y - 1 );

        m_plotter->FlashPadOval( aDrillPos, drillSize, aOrientation, GetPlotMode(), nullptr );
    }
    else
    {
        m_plotter->FlashPadCircle( aDrillPos, drillSize.x, GetPlotMode(), nullptr );
    }
}


void BRDITEMS_PLOTTER::PlotDrillMarks()
{
    int smallDrill = 0;

    if( GetDrillMarksType() == DRILL_MARKS::SMALL_DRILL_SHAPE )
        smallDrill = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize );

    /* In the filled trace mode drill marks are drawn white-on-black to knock-out the underlying
       pad.  This works only for drivers supporting color change, obviously... it means that:
       - PS, SVG and PDF output is correct (i.e. you have a 'donut' pad)
       - In HPGL you can't see them
       - In gerbers you can't see them, too. This is arguably the right thing to do since having
         drill marks and high speed drill stations is a sure recipe for broken tools and angry
         manufacturers. If you *really* want them you could start a layer with negative
         polarity to knock-out the film.
       - In DXF they go into the 'WHITE' layer. This could be useful.
     */
    if( GetPlotMode() == FILLED )
         m_plotter->SetColor( WHITE );

    for( PCB_TRACK* track : m_board->Tracks() )
    {
        if( track->Type() == PCB_VIA_T )
        {
            const PCB_VIA* via = static_cast<const PCB_VIA*>( track );

            // Via are not always on all layers
            if( ( via->GetLayerSet() & m_layerMask ).none() )
                continue;

            plotOneDrillMark( PAD_DRILL_SHAPE::CIRCLE, via->GetStart(),
                              VECTOR2I( via->GetDrillValue(), 0 ),
                              VECTOR2I( via->GetWidth( PADSTACK::ALL_LAYERS ), 0 ),
                              ANGLE_0, smallDrill );
        }
    }

    for( FOOTPRINT* footprint : m_board->Footprints() )
    {
        for( PAD* pad : footprint->Pads() )
        {
            if( pad->GetDrillSize().x == 0 )
                continue;

            if( GetPlotMode() == FILLED )
                m_plotter->SetColor( ( pad->GetLayerSet() & m_layerMask ).any() ? WHITE : BLACK );

            plotOneDrillMark( pad->GetDrillShape(), pad->GetPosition(), pad->GetDrillSize(),
                              pad->GetSize( PADSTACK::ALL_LAYERS ), pad->GetOrientation(), smallDrill );
        }
    }

    if( GetPlotMode() == FILLED )
        m_plotter->SetColor( BLACK );
}
