/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013-2019 CERN
 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
 *
 * @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
 * @author Maciej Suminski <maciej.suminski@cern.ch>
 *
 * 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 <board.h>
#include <board_design_settings.h>
#include <pcb_track.h>
#include <pcb_group.h>
#include <footprint.h>
#include <pad.h>
#include <pcb_shape.h>
#include <string_utils.h>
#include <zone.h>
#include <pcb_text.h>
#include <pcb_marker.h>
#include <pcb_dimension.h>
#include <pcb_target.h>
#include <advanced_config.h>
#include <core/arraydim.h>

#include <layer_ids.h>
#include <pcb_painter.h>
#include <pcb_display_options.h>
#include <project/net_settings.h>
#include <settings/color_settings.h>

#include <convert_basic_shapes_to_polygon.h>
#include <gal/graphics_abstraction_layer.h>
#include <geometry/geometry_utils.h>
#include <geometry/shape_line_chain.h>
#include <geometry/shape_rect.h>
#include <geometry/shape_segment.h>
#include <geometry/shape_simple.h>
#include <geometry/shape_circle.h>
#include <bezier_curves.h>

using namespace KIGFX;

PCB_RENDER_SETTINGS::PCB_RENDER_SETTINGS()
{
    m_backgroundColor = COLOR4D( 0.0, 0.0, 0.0, 1.0 );
    m_padNumbers = true;
    m_netNamesOnPads = true;
    m_netNamesOnTracks = true;
    m_netNamesOnVias = true;
    m_zoneOutlines = true;
    m_zoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
    m_clearanceDisplayFlags = CL_NONE;
    m_sketchGraphics = false;
    m_sketchText = false;
    m_netColorMode = NET_COLOR_MODE::RATSNEST;
    m_contrastModeDisplay = HIGH_CONTRAST_MODE::NORMAL;
    m_ratsnestDisplayMode = RATSNEST_MODE::ALL;

    m_trackOpacity = 1.0;
    m_viaOpacity   = 1.0;
    m_padOpacity   = 1.0;
    m_zoneOpacity  = 1.0;

    // By default everything should be displayed as filled
    for( unsigned int i = 0; i < arrayDim( m_sketchMode ); ++i )
        m_sketchMode[i] = false;

    update();
}


void PCB_RENDER_SETTINGS::LoadColors( const COLOR_SETTINGS* aSettings )
{
    SetBackgroundColor( aSettings->GetColor( LAYER_PCB_BACKGROUND ) );

    // Init board layers colors:
    for( int i = 0; i < PCB_LAYER_ID_COUNT; i++ )
    {
        m_layerColors[i] = aSettings->GetColor( i );

        // Guard: if the alpha channel is too small, the layer is not visible.
        if( m_layerColors[i].a < 0.2 )
            m_layerColors[i].a = 0.2;
    }

    // Init specific graphic layers colors:
    for( int i = GAL_LAYER_ID_START; i < GAL_LAYER_ID_END; i++ )
        m_layerColors[i] = aSettings->GetColor( i );

    // Colors for layers that aren't theme-able
    m_layerColors[LAYER_PAD_PLATEDHOLES] = aSettings->GetColor( LAYER_PCB_BACKGROUND );
    m_layerColors[LAYER_VIA_NETNAMES]    = COLOR4D( 0.2, 0.2, 0.2, 0.9 );
    m_layerColors[LAYER_PAD_NETNAMES]    = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
    m_layerColors[LAYER_PAD_FR]          = aSettings->GetColor( F_Cu );
    m_layerColors[LAYER_PAD_BK]          = aSettings->GetColor( B_Cu );
    m_layerColors[LAYER_PAD_FR_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );
    m_layerColors[LAYER_PAD_BK_NETNAMES] = COLOR4D( 1.0, 1.0, 1.0, 0.9 );

    // Netnames for copper layers
    for( LSEQ cu = LSET::AllCuMask().CuStack();  cu;  ++cu )
    {
        const COLOR4D lightLabel( 0.8, 0.8, 0.8, 0.7 );
        const COLOR4D darkLabel = lightLabel.Inverted();
        PCB_LAYER_ID  layer = *cu;

        if( m_layerColors[layer].GetBrightness() > 0.5 )
            m_layerColors[GetNetnameLayer( layer )] = darkLabel;
        else
            m_layerColors[GetNetnameLayer( layer )] = lightLabel;
    }

    update();
}


void PCB_RENDER_SETTINGS::LoadDisplayOptions( const PCB_DISPLAY_OPTIONS& aOptions,
                                              bool aShowPageLimits )
{
    m_hiContrastEnabled = ( aOptions.m_ContrastModeDisplay !=
                            HIGH_CONTRAST_MODE::NORMAL );
    m_padNumbers        = aOptions.m_DisplayPadNum;
    m_sketchGraphics    = !aOptions.m_DisplayGraphicsFill;
    m_sketchText        = !aOptions.m_DisplayTextFill;
    m_curvedRatsnestlines = aOptions.m_DisplayRatsnestLinesCurved;
    m_globalRatsnestlines = aOptions.m_ShowGlobalRatsnest;

    // Whether to draw tracks, vias & pads filled or as outlines
    m_sketchMode[LAYER_PADS_TH]      = !aOptions.m_DisplayPadFill;
    m_sketchMode[LAYER_VIA_THROUGH]  = !aOptions.m_DisplayViaFill;
    m_sketchMode[LAYER_VIA_BBLIND]   = !aOptions.m_DisplayViaFill;
    m_sketchMode[LAYER_VIA_MICROVIA] = !aOptions.m_DisplayViaFill;
    m_sketchMode[LAYER_TRACKS]       = !aOptions.m_DisplayPcbTrackFill;

    // Net names display settings
    switch( aOptions.m_DisplayNetNamesMode )
    {
    case 0:
        m_netNamesOnPads   = false;
        m_netNamesOnTracks = false;
        m_netNamesOnVias   = false;
        break;

    case 1:
        m_netNamesOnPads   = true;
        m_netNamesOnTracks = false;
        m_netNamesOnVias   = true;        // Follow pads or tracks?  For now we chose pads....
        break;

    case 2:
        m_netNamesOnPads   = false;
        m_netNamesOnTracks = true;
        m_netNamesOnVias   = false;       // Follow pads or tracks?  For now we chose pads....
        break;

    case 3:
        m_netNamesOnPads   = true;
        m_netNamesOnTracks = true;
        m_netNamesOnVias   = true;
        break;
    }

    // Zone display settings
    m_zoneDisplayMode = aOptions.m_ZoneDisplayMode;

    // Clearance settings
    switch( aOptions.m_ShowTrackClearanceMode )
    {
        case PCB_DISPLAY_OPTIONS::DO_NOT_SHOW_CLEARANCE:
            m_clearanceDisplayFlags = CL_NONE;
            break;

        case PCB_DISPLAY_OPTIONS::SHOW_TRACK_CLEARANCE_WHILE_ROUTING:
            m_clearanceDisplayFlags = CL_NEW | CL_TRACKS;
            break;

        case PCB_DISPLAY_OPTIONS::SHOW_TRACK_CLEARANCE_WITH_VIA_WHILE_ROUTING:
            m_clearanceDisplayFlags = CL_NEW | CL_TRACKS | CL_VIAS;
            break;

        case PCB_DISPLAY_OPTIONS::SHOW_WHILE_ROUTING_OR_DRAGGING:
            m_clearanceDisplayFlags = CL_NEW | CL_EDITED | CL_TRACKS | CL_VIAS;
            break;

        case PCB_DISPLAY_OPTIONS::SHOW_TRACK_CLEARANCE_WITH_VIA_ALWAYS:
            m_clearanceDisplayFlags = CL_NEW | CL_EDITED | CL_EXISTING | CL_TRACKS | CL_VIAS;
            break;
    }

    if( aOptions.m_DisplayPadClearance )
        m_clearanceDisplayFlags |= CL_PADS;

    m_contrastModeDisplay = aOptions.m_ContrastModeDisplay;

    m_netColorMode = aOptions.m_NetColorMode;

    m_ratsnestDisplayMode = aOptions.m_RatsnestMode;

    m_trackOpacity = aOptions.m_TrackOpacity;
    m_viaOpacity   = aOptions.m_ViaOpacity;
    m_padOpacity   = aOptions.m_PadOpacity;
    m_zoneOpacity  = aOptions.m_ZoneOpacity;

    m_showPageLimits = aShowPageLimits;
}


COLOR4D PCB_RENDER_SETTINGS::GetColor( const VIEW_ITEM* aItem, int aLayer ) const
{
    const EDA_ITEM*             item = dynamic_cast<const EDA_ITEM*>( aItem );
    const BOARD_CONNECTED_ITEM* conItem = dynamic_cast<const BOARD_CONNECTED_ITEM*> ( aItem );
    int                         netCode = -1;
    int                         originalLayer = aLayer;

    // Marker shadows
    if( aLayer == LAYER_MARKER_SHADOWS )
        return m_backgroundColor.WithAlpha( 0.6 );

    if( IsHoleLayer( aLayer ) && m_isPrinting )
    {
        // Careful that we don't end up with the same colour for the annular ring and the hole
        // when printing in B&W.
        const PAD*     pad = dynamic_cast<const PAD*>( item );
        const PCB_VIA* via = dynamic_cast<const PCB_VIA*>( item );
        int            holeLayer = aLayer;
        int            annularRingLayer = UNDEFINED_LAYER;

        if( pad && pad->GetAttribute() == PAD_ATTRIB::PTH )
            annularRingLayer = LAYER_PADS_TH;
        else if( via && via->GetViaType() == VIATYPE::MICROVIA )
            annularRingLayer = LAYER_VIA_MICROVIA;
        else if( via && via->GetViaType() == VIATYPE::BLIND_BURIED )
            annularRingLayer = LAYER_VIA_BBLIND;
        else if( via && via->GetViaType() == VIATYPE::THROUGH )
            annularRingLayer = LAYER_VIA_THROUGH;

        if( annularRingLayer != UNDEFINED_LAYER
                && m_layerColors[ holeLayer ] == m_layerColors[ annularRingLayer ] )
        {
            aLayer = LAYER_PCB_BACKGROUND;
        }
    }

    // Zones should pull from the copper layer
    if( item && ( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T ) )
    {
        if( IsZoneLayer( aLayer ) )
            aLayer = aLayer - LAYER_ZONE_START;
    }

    // Hole walls should pull from the copper layer
    if( aLayer == LAYER_PAD_HOLEWALLS )
        aLayer = LAYER_PADS_TH;
    else if( aLayer == LAYER_VIA_HOLEWALLS )
        aLayer = LAYER_VIA_THROUGH;

    // Normal path: get the layer base color
    COLOR4D color = m_layerColors[aLayer];

    if( !item )
        return m_layerColors[aLayer];

    // Selection disambiguation
    if( item->IsBrightened() )
        return color.Brightened( m_selectFactor ).WithAlpha( 0.8 );

    // Normal selection
    if( item->IsSelected() )
        color = m_layerColorsSel[aLayer];

    // Try to obtain the netcode for the item
    if( conItem )
        netCode = conItem->GetNetCode();

    bool highlighted = m_highlightEnabled && m_highlightNetcodes.count( netCode );
    bool selected    = item->IsSelected();

    // Apply net color overrides
    if( conItem && m_netColorMode == NET_COLOR_MODE::ALL && IsNetCopperLayer( aLayer ) )
    {
        COLOR4D netColor = COLOR4D::UNSPECIFIED;

        auto ii = m_netColors.find( netCode );

        if( ii != m_netColors.end() )
            netColor = ii->second;

        if( netColor == COLOR4D::UNSPECIFIED )
        {
            auto jj = m_netclassColors.find( conItem->GetNetClassName() );

            if( jj != m_netclassColors.end() )
                netColor = jj->second;
        }

        if( netColor == COLOR4D::UNSPECIFIED )
            netColor = color;

        if( selected )
        {
            // Selection brightening overrides highlighting
            netColor.Brighten( m_selectFactor );
        }
        else if( m_highlightEnabled )
        {
            // Highlight brightens objects on all layers and darkens everything else for contrast
            if( highlighted )
                netColor.Brighten( m_highlightFactor );
            else
                netColor.Darken( 1.0 - m_highlightFactor );
        }

        color = netColor;
    }
    else if( !selected && m_highlightEnabled )
    {
        // Single net highlight mode
        color = m_highlightNetcodes.count( netCode ) ? m_layerColorsHi[aLayer]
                                                     : m_layerColorsDark[aLayer];
    }

    // Apply high-contrast dimming
    if( m_hiContrastEnabled && m_highContrastLayers.size() && !highlighted && !selected )
    {
        PCB_LAYER_ID primary = GetPrimaryHighContrastLayer();
        bool         isActive = m_highContrastLayers.count( aLayer );

        switch( originalLayer )
        {
        case LAYER_PADS_TH:
            if( !static_cast<const PAD*>( item )->FlashLayer( primary ) )
                isActive = false;

            break;

        case LAYER_VIA_BBLIND:
        case LAYER_VIA_MICROVIA:
            // Target graphic is active if the via crosses the primary layer
            if( static_cast<const PCB_VIA*>( item )->GetLayerSet().test( primary ) == 0 )
                isActive = false;

            break;

        case LAYER_VIA_THROUGH:
            if( !static_cast<const PCB_VIA*>( item )->FlashLayer( primary ) )
                isActive = false;

            break;

        case LAYER_PAD_PLATEDHOLES:
        case LAYER_PAD_HOLEWALLS:
        case LAYER_NON_PLATEDHOLES:
            // Pad holes are active is any physical layer is active
            if( LSET::PhysicalLayersMask().test( primary ) == 0 )
                isActive = false;

            break;

        case LAYER_VIA_HOLES:
        case LAYER_VIA_HOLEWALLS:
            if( static_cast<const PCB_VIA*>( item )->GetViaType() == VIATYPE::BLIND_BURIED
                    || static_cast<const PCB_VIA*>( item )->GetViaType() == VIATYPE::MICROVIA )
            {
                // A blind or micro via's hole is active if it crosses the primary layer
                if( static_cast<const PCB_VIA*>( item )->GetLayerSet().test( primary ) == 0 )
                    isActive = false;
            }
            else
            {
                // A through via's hole is active if any physical layer is active
                if( LSET::PhysicalLayersMask().test( primary ) == 0 )
                    isActive = false;
            }

            break;

        default:
            break;
        }

        if( !isActive )
        {
            if( m_contrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN || IsNetnameLayer( aLayer ) )
                color = COLOR4D::CLEAR;
            else
                color = color.Mix( m_layerColors[LAYER_PCB_BACKGROUND], m_hiContrastFactor );
        }
    }

    // Apply per-type opacity overrides
    if( item->Type() == PCB_TRACE_T || item->Type() == PCB_ARC_T )
        color.a *= m_trackOpacity;
    else if( item->Type() == PCB_VIA_T )
        color.a *= m_viaOpacity;
    else if( item->Type() == PCB_PAD_T )
        color.a *= m_padOpacity;
    else if( item->Type() == PCB_ZONE_T || item->Type() == PCB_FP_ZONE_T )
        color.a *= m_zoneOpacity;

    // No special modifiers enabled
    return color;
}


PCB_PAINTER::PCB_PAINTER( GAL* aGal ) :
    PAINTER( aGal ),
    m_maxError( ARC_HIGH_DEF ),
    m_holePlatingThickness( 0 )
{
}


int PCB_PAINTER::getLineThickness( int aActualThickness ) const
{
    // if items have 0 thickness, draw them with the outline
    // width, otherwise respect the set value (which, no matter
    // how small will produce something)
    if( aActualThickness == 0 )
        return m_pcbSettings.m_outlineWidth;

    return aActualThickness;
}


int PCB_PAINTER::getDrillShape( const PAD* aPad ) const
{
    return aPad->GetDrillShape();
}


VECTOR2D PCB_PAINTER::getDrillSize( const PAD* aPad ) const
{
    return VECTOR2D( aPad->GetDrillSize() );
}


int PCB_PAINTER::getDrillSize( const PCB_VIA* aVia ) const
{
    return aVia->GetDrillValue();
}


bool PCB_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
{
    const BOARD_ITEM* item = dynamic_cast<const BOARD_ITEM*>( aItem );

    if( !item )
        return false;

    if( const BOARD* board = item->GetBoard() )
    {
        BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
        m_maxError = bds.m_MaxError;
        m_holePlatingThickness = bds.GetHolePlatingThickness();
    }
    else
    {
        m_maxError = ARC_HIGH_DEF;
        m_holePlatingThickness = 0;
    }

    // the "cast" applied in here clarifies which overloaded draw() is called
    switch( item->Type() )
    {
    case PCB_TRACE_T:
        draw( static_cast<const PCB_TRACK*>( item ), aLayer );
        break;

    case PCB_ARC_T:
        draw( static_cast<const PCB_ARC*>( item ), aLayer );
        break;

    case PCB_VIA_T:
        draw( static_cast<const PCB_VIA*>( item ), aLayer );
        break;

    case PCB_PAD_T:
        draw( static_cast<const PAD*>( item ), aLayer );
        break;

    case PCB_SHAPE_T:
    case PCB_FP_SHAPE_T:
        draw( static_cast<const PCB_SHAPE*>( item ), aLayer );
        break;

    case PCB_TEXT_T:
        draw( static_cast<const PCB_TEXT*>( item ), aLayer );
        break;

    case PCB_FP_TEXT_T:
        draw( static_cast<const FP_TEXT*>( item ), aLayer );
        break;

    case PCB_FOOTPRINT_T:
        draw( static_cast<const FOOTPRINT*>( item ), aLayer );
        break;

    case PCB_GROUP_T:
        draw( static_cast<const PCB_GROUP*>( item ), aLayer );
        break;

    case PCB_ZONE_T:
        draw( static_cast<const ZONE*>( item ), aLayer );
        break;

    case PCB_FP_ZONE_T:
        draw( static_cast<const ZONE*>( item ), aLayer );
        break;

    case PCB_DIM_ALIGNED_T:
    case PCB_DIM_CENTER_T:
    case PCB_DIM_ORTHOGONAL_T:
    case PCB_DIM_LEADER_T:
        draw( static_cast<const PCB_DIMENSION_BASE*>( item ), aLayer );
        break;

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

    case PCB_MARKER_T:
        draw( static_cast<const PCB_MARKER*>( item ), aLayer );
        break;

    default:
        // Painter does not know how to draw the object
        return false;
    }

    // Draw bounding boxes after drawing objects so they can be seen.
    if( ADVANCED_CFG::GetCfg().m_DrawBoundingBoxes )
    {
        // Show bounding boxes of painted objects for debugging.
        EDA_RECT box = item->GetBoundingBox();
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );

        if( item->Type() == PCB_FOOTPRINT_T )
        {
            m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
                                   COLOR4D( MAGENTA ) );
        }
        else
        {
            m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
                                   COLOR4D( 0.4, 0.4, 0.4, 1 ) );
        }

        m_gal->SetLineWidth( 1 );
        m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );

        if( item->Type() == PCB_FOOTPRINT_T )
        {
            m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
                                   COLOR4D( CYAN ) );

            const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( item );

            if( fp )
            {
                SHAPE_POLY_SET convex = fp->GetBoundingHull();

                m_gal->DrawPolyline( convex.COutline( 0 ) );
            }
        }
    }

    return true;
}


void PCB_PAINTER::draw( const PCB_TRACK* aTrack, int aLayer )
{
    VECTOR2D start( aTrack->GetStart() );
    VECTOR2D end( aTrack->GetEnd() );
    int      width = aTrack->GetWidth();
    COLOR4D  color = m_pcbSettings.GetColor( aTrack, aLayer );

    if( IsNetnameLayer( aLayer ) )
    {
        if( !m_pcbSettings.m_netNamesOnTracks )
            return;

        if( aTrack->GetNetCode() <= NETINFO_LIST::UNCONNECTED )
            return;

        VECTOR2D line = ( end - start );
        double length = line.EuclideanNorm();

        // Check if the track is long enough to have a netname displayed
        if( length < 10 * width )
            return;

        const wxString& netName = UnescapeString( aTrack->GetShortNetname() );
        double   textSize = width;
        double   penWidth = width / 12.0;
        VECTOR2D textPosition = start + line / 2.0;     // center of the track
        double   textOrientation;

        if( end.y == start.y ) // horizontal
        {
            textOrientation = 0;
            textPosition.y += penWidth;
        }
        else if( end.x == start.x ) // vertical
        {
            textOrientation = M_PI / 2;
            textPosition.x += penWidth;
        }
        else
        {
            textOrientation = -atan( line.y / line.x );
            textPosition.x += penWidth / 1.4;
            textPosition.y += penWidth / 1.4;
        }


        m_gal->SetIsStroke( true );
        m_gal->SetIsFill( false );
        m_gal->SetStrokeColor( color );
        m_gal->SetLineWidth( penWidth );
        m_gal->SetFontBold( false );
        m_gal->SetFontItalic( false );
        m_gal->SetFontUnderlined( false );
        m_gal->SetTextMirrored( false );
        m_gal->SetGlyphSize( VECTOR2D( textSize * 0.55, textSize * 0.55 ) );
        m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
        m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
        m_gal->BitmapText( netName, textPosition, textOrientation );

        return;
    }
    else if( IsCopperLayer( aLayer ) )
    {
        // Draw a regular track
        bool outline_mode = m_pcbSettings.m_sketchMode[LAYER_TRACKS];
        m_gal->SetStrokeColor( color );
        m_gal->SetFillColor( color );
        m_gal->SetIsStroke( outline_mode );
        m_gal->SetIsFill( not outline_mode );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );

        m_gal->DrawSegment( start, end, width );
    }

    // Clearance lines
    constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING
                                 | PCB_RENDER_SETTINGS::CL_TRACKS;

    if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags )
    {
        int clearance = aTrack->GetOwnClearance( m_pcbSettings.GetActiveLayer() );

        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetStrokeColor( color );
        m_gal->DrawSegment( start, end, width + clearance * 2 );
    }
}


void PCB_PAINTER::draw( const PCB_ARC* aArc, int aLayer )
{
    VECTOR2D center( aArc->GetCenter() );
    int      width = aArc->GetWidth();
    COLOR4D  color = m_pcbSettings.GetColor( aArc, aLayer );
    double   radius = aArc->GetRadius();
    double   start_angle = DECIDEG2RAD( aArc->GetArcAngleStart() );
    double   angle = DECIDEG2RAD( aArc->GetAngle() );

    if( IsNetnameLayer( aLayer ) )
    {
        // Ummm, yeah.  Anyone fancy implementing text on a path?
        return;
    }
    else if( IsCopperLayer( aLayer ) )
    {
        // Draw a regular track
        bool outline_mode = m_pcbSettings.m_sketchMode[LAYER_TRACKS];
        m_gal->SetStrokeColor( color );
        m_gal->SetFillColor( color );
        m_gal->SetIsStroke( outline_mode );
        m_gal->SetIsFill( not outline_mode );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );

        m_gal->DrawArcSegment( center, radius, start_angle, start_angle + angle, width, m_maxError );
    }

    // Clearance lines
    constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING
                                 | PCB_RENDER_SETTINGS::CL_TRACKS;

    if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags )
    {
        int clearance = aArc->GetOwnClearance( m_pcbSettings.GetActiveLayer() );

        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetStrokeColor( color );

        m_gal->DrawArcSegment( center, radius, start_angle, start_angle + angle,
                               width + clearance * 2, m_maxError );
    }

// Debug only: enable this code only to test the TransformArcToPolygon function
// and display the polygon outline created by it.
// arcs on F_Cu are approximated with ERROR_INSIDE, others with ERROR_OUTSIDE
#if 0
    SHAPE_POLY_SET cornerBuffer;
    ERROR_LOC errorloc = aLayer == F_Cu ? ERROR_LOC::ERROR_INSIDE : ERROR_LOC::ERROR_OUTSIDE;
    TransformArcToPolygon( cornerBuffer, aArc->GetStart(), aArc->GetMid(), aArc->GetEnd(), width,
                           m_maxError, errorloc );
    m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );
    m_gal->SetStrokeColor( COLOR4D( 0, 0, 1.0, 1.0 ) );
    m_gal->DrawPolygon( cornerBuffer );
#endif

// Debug only: enable this code only to test the SHAPE_ARC::ConvertToPolyline function
// and display the polyline created by it.
#if 0
    SHAPE_ARC arc( aArc->GetCenter(), aArc->GetStart(), aArc->GetAngle() / 10.0, aArc->GetWidth() );
    SHAPE_LINE_CHAIN arcSpine = arc.ConvertToPolyline( m_maxError );
    m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );
    m_gal->SetStrokeColor( COLOR4D( 0.3, 0.2, 0.5, 1.0 ) );

    for( int idx = 1; idx < arcSpine.PointCount(); idx++ )
        m_gal->DrawSegment( arcSpine.CPoint( idx-1 ), arcSpine.CPoint( idx ), aArc->GetWidth() );
#endif
}


void PCB_PAINTER::draw( const PCB_VIA* aVia, int aLayer )
{
    COLOR4D  color = m_pcbSettings.GetColor( aVia, aLayer );
    VECTOR2D center( aVia->GetStart() );

    if( color == COLOR4D::CLEAR )
        return;

    // Draw description layer
    if( IsNetnameLayer( aLayer ) )
    {
        VECTOR2D position( center );

        // Is anything that we can display enabled?
        if( !m_pcbSettings.m_netNamesOnVias || aVia->GetNetname().empty() )
            return;

        double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
        double size = aVia->GetWidth();

        // Font size limits
        if( size > maxSize )
            size = maxSize;

        m_gal->Save();
        m_gal->Translate( position );

        // Default font settings
        m_gal->ResetTextAttributes();
        m_gal->SetStrokeColor( m_pcbSettings.GetColor( nullptr, aLayer ) );

        // Set the text position to the pad shape position (the pad position is not the best place)
        VECTOR2D textpos( 0.0, 0.0 );

        wxString netname = UnescapeString( aVia->GetShortNetname() );

        // approximate the size of net name text:
        double tsize = 1.5 * size / std::max( PrintableCharCount( netname ), 1 );
        tsize = std::min( tsize, size );

        // Use a smaller text size to handle interline, pen size..
        tsize *= 0.7;
        VECTOR2D namesize( tsize, tsize );

        m_gal->SetGlyphSize( namesize );
        m_gal->SetLineWidth( namesize.x / 12.0 );
        m_gal->BitmapText( netname, textpos, 0.0 );

        m_gal->Restore();

        return;
    }
    else if( aLayer == LAYER_VIA_HOLEWALLS )
    {
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetStrokeColor( color );
        m_gal->SetLineWidth( m_holePlatingThickness );

        m_gal->DrawCircle( center, ( getDrillSize( aVia ) + m_holePlatingThickness ) / 2.0 );

        return;
    }

    bool sketchMode = false;

    switch( aVia->GetViaType() )
    {
    case VIATYPE::THROUGH:      sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_THROUGH];  break;
    case VIATYPE::BLIND_BURIED: sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_BBLIND];   break;
    case VIATYPE::MICROVIA:     sketchMode = m_pcbSettings.m_sketchMode[LAYER_VIA_MICROVIA]; break;
    default:                    wxASSERT( false );                                           break;
    }

    if( sketchMode )
    {
        // Outline mode
        m_gal->SetIsStroke( true );
        m_gal->SetIsFill( false );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetStrokeColor( color );
    }
    else
    {
        // Filled mode
        m_gal->SetIsFill( true );
        m_gal->SetIsStroke( false );
        m_gal->SetFillColor( color );
    }

    if( aLayer == LAYER_VIA_HOLES )
    {
        m_gal->DrawCircle( center, getDrillSize( aVia ) / 2.0 );
    }
    else if( aLayer == LAYER_VIA_THROUGH || m_pcbSettings.GetDrawIndividualViaLayers() )
    {
        m_gal->DrawCircle( center, aVia->GetWidth() / 2.0 );
    }
    else if( aLayer == LAYER_VIA_BBLIND || aLayer == LAYER_VIA_MICROVIA )
    {
        // Outer circles of blind/buried and micro-vias are drawn in a special way to indicate the
        // top and bottom layers
        PCB_LAYER_ID layerTop, layerBottom;
        aVia->LayerPair( &layerTop, &layerBottom );

        double radius =  aVia->GetWidth() / 2.0;

        if( !sketchMode )
            m_gal->SetLineWidth( ( aVia->GetWidth() - aVia->GetDrillValue() ) / 2.0 );

        m_gal->DrawArc( center, radius, M_PI * -0.375, M_PI * 0.375 );
        m_gal->DrawArc( center, radius, M_PI * 0.625, M_PI * 1.375 );

        if( sketchMode )
            m_gal->SetStrokeColor( m_pcbSettings.GetColor( aVia, layerTop ) );
        else
            m_gal->SetFillColor( m_pcbSettings.GetColor( aVia, layerTop ) );

        m_gal->DrawArc( center, radius, M_PI * 1.375, M_PI * 1.625 );

        if( sketchMode )
            m_gal->SetStrokeColor( m_pcbSettings.GetColor( aVia, layerBottom ) );
        else
            m_gal->SetFillColor( m_pcbSettings.GetColor( aVia, layerBottom ) );

        m_gal->DrawArc( center, radius, M_PI * 0.375, M_PI * 0.625 );
    }

    // Clearance lines
    constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_EXISTING | PCB_RENDER_SETTINGS::CL_VIAS;

    if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags
            && aLayer != LAYER_VIA_HOLES )
    {
        PCB_LAYER_ID activeLayer = m_pcbSettings.GetActiveLayer();
        double       radius;

        if( aVia->FlashLayer( activeLayer ) )
            radius = aVia->GetWidth() / 2.0;
        else
            radius = getDrillSize( aVia ) / 2.0 + m_holePlatingThickness;

        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetStrokeColor( color );
        m_gal->DrawCircle( center, radius + aVia->GetOwnClearance( activeLayer ) );
    }
}


void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
{
    COLOR4D                color = m_pcbSettings.GetColor( aPad, aLayer );

    if( IsNetnameLayer( aLayer ) )
    {
        // Is anything that we can display enabled?
        if( m_pcbSettings.m_netNamesOnPads || m_pcbSettings.m_padNumbers )
        {
            bool displayNetname = ( m_pcbSettings.m_netNamesOnPads && !aPad->GetNetname().empty() );
            EDA_RECT padBBox = aPad->GetBoundingBox();
            VECTOR2D position = padBBox.Centre();
            VECTOR2D padsize = VECTOR2D( padBBox.GetSize() );

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

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

            double maxSize = PCB_RENDER_SETTINGS::MAX_FONT_SIZE;
            double size = padsize.y;

            m_gal->Save();
            m_gal->Translate( position );

            // Keep the size ratio for the font, but make it smaller
            if( padsize.x < padsize.y )
            {
                m_gal->Rotate( DECIDEG2RAD( -900.0 ) );
                size = padsize.x;
                std::swap( padsize.x, padsize.y );
            }

            // Font size limits
            if( size > maxSize )
                size = maxSize;

            // Default font settings
            m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
            m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_CENTER );
            m_gal->SetFontBold( false );
            m_gal->SetFontItalic( false );
            m_gal->SetFontUnderlined( false );
            m_gal->SetTextMirrored( false );
            m_gal->SetStrokeColor( m_pcbSettings.GetColor( aPad, aLayer ) );
            m_gal->SetIsStroke( true );
            m_gal->SetIsFill( false );

            // We have already translated the GAL to be centered at the center of the pad's
            // bounding box
            VECTOR2D textpos( 0.0, 0.0 );

            // Divide the space, to display both pad numbers and netnames and set the Y text
            // position to display 2 lines
            if( displayNetname && m_pcbSettings.m_padNumbers )
            {
                size = size / 2.5;
                textpos.y = size / 1.7;
            }

            if( displayNetname )
            {
                wxString netname = UnescapeString( aPad->GetShortNetname() );
                wxString pinType = aPad->GetPinType();

                // If the pad is actually not connected (unique pad in the net),
                // shorten the displayed netname (actual name not useful)
                // Can happen if the pad netname is edited inside the board editor, therefore
                // having a netname not coming from schematic
                if( netname.StartsWith( "unconnected-(" ) )
                {
                    if( pinType == wxT( "no_connect" ) || pinType.EndsWith( wxT( "+no_connect" ) ) )
                        netname = "x";
                    else if( pinType == wxT( "free" ) )
                        netname = "*";
                }

                // approximate the size of net name text:
                double tsize = 1.5 * padsize.x / std::max( PrintableCharCount( netname ), 1 );
                tsize = std::min( tsize, size );

                // Use a smaller text size to handle interline, pen size...
                tsize *= 0.7;
                VECTOR2D namesize( tsize, tsize );

                m_gal->SetGlyphSize( namesize );
                m_gal->SetLineWidth( namesize.x / 12.0 );
                m_gal->BitmapText( netname, textpos, 0.0 );
            }

            if( m_pcbSettings.m_padNumbers )
            {
                const wxString& padNumber = aPad->GetNumber();
                textpos.y = -textpos.y;

                // approximate the size of the pad number text:
                double tsize = 1.5 * padsize.x / std::max( PrintableCharCount( padNumber ), 1 );
                tsize = std::min( tsize, size );

                // Use a smaller text size to handle interline, pen size...
                tsize *= 0.7;
                tsize = std::min( tsize, size );
                VECTOR2D numsize( tsize, tsize );

                m_gal->SetGlyphSize( numsize );
                m_gal->SetLineWidth( numsize.x / 12.0 );
                m_gal->BitmapText( padNumber, textpos, 0.0 );
            }

            m_gal->Restore();
        }

        return;
    }
    else if( aLayer == LAYER_PAD_HOLEWALLS )
    {
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetLineWidth( m_holePlatingThickness );
        m_gal->SetStrokeColor( color );

        const SHAPE_SEGMENT* seg = aPad->GetEffectiveHoleShape();
        int                  holeSize = seg->GetWidth() + m_holePlatingThickness;

        if( seg->GetSeg().A == seg->GetSeg().B )    // Circular hole
            m_gal->DrawCircle( seg->GetSeg().A, holeSize / 2 );
        else
            m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B, holeSize );

        return;
    }

    if( m_pcbSettings.m_sketchMode[LAYER_PADS_TH] )
    {
        // Outline mode
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetStrokeColor( color );
    }
    else
    {
        // Filled mode
        m_gal->SetIsFill( true );
        m_gal->SetIsStroke( false );
        m_gal->SetFillColor( color );
    }

    if( aLayer == LAYER_PAD_PLATEDHOLES || aLayer == LAYER_NON_PLATEDHOLES )
    {
        const SHAPE_SEGMENT* seg = aPad->GetEffectiveHoleShape();

        if( seg->GetSeg().A == seg->GetSeg().B )    // Circular hole
            m_gal->DrawCircle( seg->GetSeg().A, getDrillSize( aPad ).x / 2 );
        else
            m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B, seg->GetWidth() );
    }
    else
    {
        wxSize pad_size = aPad->GetSize();
        wxSize margin;

        switch( aLayer )
        {
        case F_Mask:
        case B_Mask:
            margin.x = margin.y = aPad->GetSolderMaskMargin();
            break;

        case F_Paste:
        case B_Paste:
            margin = aPad->GetSolderPasteMargin();
            break;

        default:
            margin.x = margin.y = 0;
            break;
        }

        std::unique_ptr<PAD>            dummyPad;
        std::shared_ptr<SHAPE_COMPOUND> shapes;

        // Drawing components of compound shapes in outline mode produces a mess.
        bool simpleShapes = !m_pcbSettings.m_sketchMode[LAYER_PADS_TH];

        if( simpleShapes )
        {
            if( ( margin.x != margin.y && aPad->GetShape() != PAD_SHAPE::CUSTOM )
                || ( aPad->GetShape() == PAD_SHAPE::ROUNDRECT && ( margin.x < 0 || margin.y < 0 ) ) )
            {
                // Our algorithms below (polygon inflation in particular) can't handle differential
                // inflation along separate axes.  So for those cases we build a dummy pad instead,
                // and inflate it.

                // Margin is added to both sides.  If the total margin is larger than the pad
                // then don't display this layer
                if( pad_size.x + 2 * margin.x <= 0 || pad_size.y + 2 * margin.y <= 0 )
                    return;

                dummyPad.reset( static_cast<PAD*>( aPad->Duplicate() ) );
                int initial_radius = dummyPad->GetRoundRectCornerRadius();

                dummyPad->SetSize( pad_size + margin + margin );

                if( dummyPad->GetShape() == PAD_SHAPE::ROUNDRECT )
                {
                    // To keep the right margin around the corners, we need to modify the corner radius.
                    // We must have only one radius correction, so use the smallest absolute margin.
                    int radius_margin = std::max( margin.x, margin.y );     // radius_margin is < 0
                    dummyPad->SetRoundRectCornerRadius( std::max( initial_radius + radius_margin, 0 ) );
                }

                shapes = std::dynamic_pointer_cast<SHAPE_COMPOUND>( dummyPad->GetEffectiveShape() );
                margin.x = margin.y = 0;
            }
            else
            {
                shapes = std::dynamic_pointer_cast<SHAPE_COMPOUND>( aPad->GetEffectiveShape() );
            }

            if( aPad->GetShape() == PAD_SHAPE::CUSTOM && ( margin.x || margin.y ) )
            {
                // We can't draw as shapes because we don't know which edges are internal and which
                // are external (so we don't know when to apply the margin and when not to).
                simpleShapes = false;
            }

            for( const SHAPE* shape : shapes->Shapes() )
            {
                if( !simpleShapes )
                    break;

                switch( shape->Type() )
                {
                case SH_SEGMENT:
                case SH_CIRCLE:
                case SH_RECT:
                case SH_SIMPLE:
                    // OK so far
                    break;

                default:
                    // Not OK
                    simpleShapes = false;
                    break;
                }
            }
        }

        if( simpleShapes )
        {
            for( const SHAPE* shape : shapes->Shapes() )
            {
                switch( shape->Type() )
                {
                case SH_SEGMENT:
                {
                    const SHAPE_SEGMENT* seg = (const SHAPE_SEGMENT*) shape;
                    int                  effectiveWidth = seg->GetWidth() + 2 * margin.x;

                    if( effectiveWidth > 0 )
                        m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B, effectiveWidth );

                    break;
                }

                case SH_CIRCLE:
                {
                    const SHAPE_CIRCLE* circle = (const SHAPE_CIRCLE*) shape;
                    int                 effectiveRadius = circle->GetRadius() + margin.x;

                    if( effectiveRadius > 0 )
                        m_gal->DrawCircle( circle->GetCenter(), effectiveRadius );

                    break;
                }

                case SH_RECT:
                {
                    const SHAPE_RECT* r = (const SHAPE_RECT*) shape;
                    VECTOR2I          pos = r->GetPosition();
                    VECTOR2I          effectiveMargin = margin;

                    if( effectiveMargin.x < 0 )
                    {
                        // A negative margin just produces a smaller rect.
                        VECTOR2I effectiveSize = r->GetSize() + effectiveMargin;

                        if( effectiveSize.x > 0 && effectiveSize.y > 0 )
                            m_gal->DrawRectangle( pos - effectiveMargin, pos + effectiveSize );
                    }
                    else if( effectiveMargin.x > 0 )
                    {
                        // A positive margin produces a larger rect, but with rounded corners
                        m_gal->DrawRectangle( r->GetPosition(), r->GetPosition() + r->GetSize() );

                        // Use segments to produce the margin with rounded corners
                        m_gal->DrawSegment( pos,
                                            pos + VECTOR2I( r->GetWidth(), 0 ),
                                            effectiveMargin.x * 2 );
                        m_gal->DrawSegment( pos + VECTOR2I( r->GetWidth(), 0 ),
                                            pos + r->GetSize(),
                                            effectiveMargin.x * 2 );
                        m_gal->DrawSegment( pos + r->GetSize(),
                                            pos + VECTOR2I( 0, r->GetHeight() ),
                                            effectiveMargin.x * 2 );
                        m_gal->DrawSegment( pos + VECTOR2I( 0, r->GetHeight() ),
                                            pos,
                                            effectiveMargin.x * 2 );
                    }
                    else
                    {
                        m_gal->DrawRectangle( r->GetPosition(), r->GetPosition() + r->GetSize() );
                    }

                    break;
                }

                case SH_SIMPLE:
                {
                    const SHAPE_SIMPLE* poly = static_cast<const SHAPE_SIMPLE*>( shape );

                    if( margin.x < 0 )  // The poly shape must be deflated
                    {
                        int numSegs = GetArcToSegmentCount( -margin.x, m_maxError, 360.0 );
                        SHAPE_POLY_SET outline;
                        outline.NewOutline();

                        for( int ii = 0; ii < poly->PointCount(); ++ii )
                            outline.Append( poly->CPoint( ii ) );

                        outline.Deflate( -margin.x, numSegs );

                        m_gal->DrawPolygon( outline );
                    }
                    else
                    {
                        m_gal->DrawPolygon( poly->Vertices() );
                    }

                    // Now add on a rounded margin (using segments) if the margin > 0
                    if( margin.x > 0 )
                    {
                        for( size_t ii = 0; ii < poly->GetSegmentCount(); ++ii )
                        {
                            SEG seg = poly->GetSegment( ii );
                            m_gal->DrawSegment( seg.A, seg.B, margin.x * 2 );
                        }
                    }

                    break;
                }

                default:
                    // Better not get here; we already pre-flighted the shapes...
                    break;
                }
            }
        }
        else
        {
            // This is expensive.  Avoid if possible.
            SHAPE_POLY_SET polySet;
            aPad->TransformShapeWithClearanceToPolygon( polySet, ToLAYER_ID( aLayer ), margin.x,
                                                        m_maxError, ERROR_INSIDE );
            m_gal->DrawPolygon( polySet );
        }
    }

    constexpr int clearanceFlags = PCB_RENDER_SETTINGS::CL_PADS;

    if( ( m_pcbSettings.m_clearanceDisplayFlags & clearanceFlags ) == clearanceFlags
            && ( aLayer == LAYER_PAD_FR || aLayer == LAYER_PAD_BK || aLayer == LAYER_PADS_TH ) )
    {
        /* Showing the clearance area is not obvious.
         * - A pad can be removed from some copper layers.
         * - For non copper layers, what is the clearance area?
         * So for copper layers, the clearance area is the shape if the pad is flashed on this
         * layer and the hole clearance area for other copper layers.
         * For other layers, use the pad shape, although one can use an other criteria,
         * depending on the non copper layer.
         */
        int  activeLayer = m_pcbSettings.GetActiveLayer();
        bool flashActiveLayer = true;

        if( IsCopperLayer( activeLayer ) )
            flashActiveLayer = aPad->FlashLayer( activeLayer );

        if( flashActiveLayer || aPad->GetDrillSize().x )
        {
            m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
            m_gal->SetIsStroke( true );
            m_gal->SetIsFill( false );
            m_gal->SetStrokeColor( color );

            int clearance = aPad->GetOwnClearance( m_pcbSettings.GetActiveLayer() );

            if( flashActiveLayer && clearance > 0 )
            {
                auto shape = std::dynamic_pointer_cast<SHAPE_COMPOUND>( aPad->GetEffectiveShape() );

                if( shape && shape->Size() == 1 && shape->Shapes()[0]->Type() == SH_SEGMENT )
                {
                    const SHAPE_SEGMENT* seg = (SHAPE_SEGMENT*) shape->Shapes()[0];
                    m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B,
                                        seg->GetWidth() + 2 * clearance );
                }
                else if( shape && shape->Size() == 1 && shape->Shapes()[0]->Type() == SH_CIRCLE )
                {
                    const SHAPE_CIRCLE* circle = (SHAPE_CIRCLE*) shape->Shapes()[0];
                    m_gal->DrawCircle( circle->GetCenter(), circle->GetRadius() + clearance );
                }
                else
                {
                    SHAPE_POLY_SET polySet;

                    // Use ERROR_INSIDE because it avoids Clipper and is therefore much faster.
                    aPad->TransformShapeWithClearanceToPolygon( polySet, ToLAYER_ID( aLayer ),
                                                                clearance, m_maxError, ERROR_INSIDE );
                    m_gal->DrawPolygon( polySet );
                }
            }
            else if( aPad->GetEffectiveHoleShape() && clearance > 0 )
            {
                clearance += m_holePlatingThickness;

                const SHAPE_SEGMENT* seg = aPad->GetEffectiveHoleShape();
                m_gal->DrawSegment( seg->GetSeg().A, seg->GetSeg().B,
                                    seg->GetWidth() + 2 * clearance );
            }
        }
    }
}


void PCB_PAINTER::draw( const PCB_SHAPE* aShape, int aLayer )
{
    const COLOR4D& color = m_pcbSettings.GetColor( aShape, aShape->GetLayer() );
    bool           sketch = m_pcbSettings.m_sketchGraphics;
    int            thickness = getLineThickness( aShape->GetWidth() );

    if( sketch )
    {
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    }

    m_gal->SetFillColor( color );
    m_gal->SetStrokeColor( color );

    switch( aShape->GetShape() )
    {
    case SHAPE_T::SEGMENT:
        if( sketch )
        {
            m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness );
        }
        else
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( false );

            m_gal->DrawSegment( aShape->GetStart(), aShape->GetEnd(), thickness );
        }

        break;

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

        if( sketch )
        {
            m_gal->DrawSegment( pts[0], pts[1], thickness );
            m_gal->DrawSegment( pts[1], pts[2], thickness );
            m_gal->DrawSegment( pts[2], pts[3], thickness );
            m_gal->DrawSegment( pts[3], pts[0], thickness );
        }
        else
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( false );

            if( thickness > 0 )
            {
                m_gal->DrawSegment( pts[0], pts[1], thickness );
                m_gal->DrawSegment( pts[1], pts[2], thickness );
                m_gal->DrawSegment( pts[2], pts[3], thickness );
                m_gal->DrawSegment( pts[3], pts[0], thickness );
            }

            if( aShape->IsFilled() )
            {
                SHAPE_POLY_SET poly;
                poly.NewOutline();

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

                m_gal->DrawPolygon( poly );
            }
        }

        break;
    }

    case SHAPE_T::ARC:
    {
        double startAngle;
        double endAngle;
        aShape->CalcArcAngles( startAngle, endAngle );

        if( sketch )
        {
            m_gal->DrawArcSegment( aShape->GetCenter(), aShape->GetRadius(),
                                   DEG2RAD( startAngle ), DEG2RAD( endAngle ), thickness,
                                   m_maxError );
        }
        else
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( false );

            m_gal->DrawArcSegment( aShape->GetCenter(), aShape->GetRadius(),
                                   DEG2RAD( startAngle ), DEG2RAD( endAngle ), thickness,
                                   m_maxError );
        }
        break;
    }

    case SHAPE_T::CIRCLE:
        if( sketch )
        {
            m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() - thickness / 2 );
            m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() + thickness / 2 );
        }
        else
        {
            m_gal->SetIsFill( aShape->IsFilled() );
            m_gal->SetIsStroke( thickness > 0 );
            m_gal->SetLineWidth( thickness );

            m_gal->DrawCircle( aShape->GetStart(), aShape->GetRadius() );
        }
        break;

    case SHAPE_T::POLY:
    {
        SHAPE_POLY_SET&  shape = const_cast<PCB_SHAPE*>( aShape )->GetPolyShape();
        const FOOTPRINT* parentFootprint = aShape->GetParentFootprint();

        if( shape.OutlineCount() == 0 )
            break;

        if( parentFootprint )
        {
            m_gal->Save();
            m_gal->Translate( parentFootprint->GetPosition() );
            m_gal->Rotate( -parentFootprint->GetOrientationRadians() );
        }

        if( sketch )
        {
            for( int ii = 0; ii < shape.Outline( 0 ).SegmentCount(); ++ii )
            {
                SEG seg = shape.Outline( 0 ).Segment( ii );
                m_gal->DrawSegment( seg.A, seg.B, thickness );
            }
        }
        else
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( false );

            if( thickness > 0 )
            {
                for( int ii = 0; ii < shape.Outline( 0 ).SegmentCount(); ++ii )
                {
                    SEG seg = shape.Outline( 0 ).Segment( ii );
                    m_gal->DrawSegment( seg.A, seg.B, thickness );
                }
            }

            if( aShape->IsFilled() )
            {
                // On Opengl, a not convex filled polygon is usually drawn by using triangles
                // as primitives. CacheTriangulation() can create basic triangle primitives to
                // draw the polygon solid shape on Opengl.  GLU tessellation is much slower, so
                // currently we are using our tessellation.
                if( m_gal->IsOpenGlEngine() && !shape.IsTriangulationUpToDate() )
                    shape.CacheTriangulation();

                m_gal->DrawPolygon( shape );
            }
        }

        if( parentFootprint )
            m_gal->Restore();

        break;
    }

    case SHAPE_T::BEZIER:
        if( sketch )
        {
            std::vector<VECTOR2D> output;
            std::vector<VECTOR2D> pointCtrl;

            pointCtrl.push_back( aShape->GetStart() );
            pointCtrl.push_back( aShape->GetBezierC1() );
            pointCtrl.push_back( aShape->GetBezierC2() );
            pointCtrl.push_back( aShape->GetEnd() );

            BEZIER_POLY converter( pointCtrl );
            converter.GetPoly( output, thickness );

            for( unsigned ii = 0; ii + 1 < output.size(); ++ii )
                m_gal->DrawSegment( output[ii], output[ii+1], thickness );
        }
        else
        {
            m_gal->SetIsFill( aShape->IsFilled() );
            m_gal->SetIsStroke( thickness > 0 );
            m_gal->SetLineWidth( thickness );

            // Use thickness as filter value to convert the curve to polyline when the curve
            // is not supported
            m_gal->DrawCurve( VECTOR2D( aShape->GetStart() ),
                              VECTOR2D( aShape->GetBezierC1() ),
                              VECTOR2D( aShape->GetBezierC2() ),
                              VECTOR2D( aShape->GetEnd() ), thickness );
        }

        break;

    case SHAPE_T::LAST:
        break;
    }
}


void PCB_PAINTER::draw( const PCB_TEXT* aText, int aLayer )
{
    wxString shownText( aText->GetShownText() );

    if( shownText.Length() == 0 )
        return;

    const COLOR4D& color = m_pcbSettings.GetColor( aText, aText->GetLayer() );
    VECTOR2D       position( aText->GetTextPos().x, aText->GetTextPos().y );

    if( m_pcbSettings.m_sketchText || m_pcbSettings.m_sketchMode[aLayer] )
    {
        // Outline mode
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    }
    else
    {
        // Filled mode
        m_gal->SetLineWidth( getLineThickness( aText->GetEffectiveTextPenWidth() ) );
    }

    m_gal->SetStrokeColor( color );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );
    m_gal->SetTextAttributes( aText );
    m_gal->StrokeText( shownText, position, aText->GetTextAngleRadians() );
}


void PCB_PAINTER::draw( const FP_TEXT* aText, int aLayer )
{
    wxString shownText( aText->GetShownText() );

    if( shownText.Length() == 0 )
        return;

    const COLOR4D& color = m_pcbSettings.GetColor( aText, aLayer );
    VECTOR2D       position( aText->GetTextPos().x, aText->GetTextPos().y );

    if( m_pcbSettings.m_sketchText )
    {
        // Outline mode
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    }
    else
    {
        // Filled mode
        m_gal->SetLineWidth( getLineThickness( aText->GetEffectiveTextPenWidth() ) );
    }

    m_gal->SetStrokeColor( color );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );
    m_gal->SetTextAttributes( aText );
    m_gal->StrokeText( shownText, position, aText->GetDrawRotationRadians() );

    // Draw the umbilical line
    if( aText->IsSelected() )
    {
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
        m_gal->SetStrokeColor( m_pcbSettings.GetColor( nullptr, LAYER_ANCHOR ) );
        m_gal->DrawLine( position, aText->GetParent()->GetPosition() );
    }
}


void PCB_PAINTER::draw( const FOOTPRINT* aFootprint, int aLayer )
{
    if( aLayer == LAYER_ANCHOR )
    {
        const COLOR4D color = m_pcbSettings.GetColor( aFootprint, aLayer );

        // Keep the size and width constant, not related to the scale because the anchor
        // is just a marker on screen
        double anchorSize = 5.0 / m_gal->GetWorldScale();           // 5 pixels size
        double anchorThickness = 1.0 / m_gal->GetWorldScale();      // 1 pixels width

        // Draw anchor
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetStrokeColor( color );
        m_gal->SetLineWidth( anchorThickness );

        VECTOR2D center = aFootprint->GetPosition();
        m_gal->DrawLine( center - VECTOR2D( anchorSize, 0 ), center + VECTOR2D( anchorSize, 0 ) );
        m_gal->DrawLine( center - VECTOR2D( 0, anchorSize ), center + VECTOR2D( 0, anchorSize ) );
    }
}


void PCB_PAINTER::draw( const PCB_GROUP* aGroup, int aLayer )
{
    if( aLayer == LAYER_ANCHOR )
    {
        if( aGroup->IsSelected() && !( aGroup->GetParent() && aGroup->GetParent()->IsSelected() ) )
        {
            // Selected on our own; draw enclosing box
        }
        else if( aGroup->IsEntered() )
        {
            // Entered group; draw enclosing box
        }
        else
        {
            // Neither selected nor entered; draw nothing at the group level (ie: only draw
            // its members)
            return;
        }

        const COLOR4D color = m_pcbSettings.GetColor( aGroup, LAYER_ANCHOR );

        EDA_RECT bbox = aGroup->GetBoundingBox();
        m_gal->SetStrokeColor( color );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth * 2.0f );
        wxPoint topLeft = bbox.GetPosition();
        wxPoint width = wxPoint( bbox.GetWidth(), 0 );
        wxPoint height = wxPoint( 0, bbox.GetHeight() );

        m_gal->DrawLine( topLeft, topLeft + width );
        m_gal->DrawLine( topLeft + width, topLeft + width + height );
        m_gal->DrawLine( topLeft + width + height, topLeft + height );
        m_gal->DrawLine( topLeft + height, topLeft );

        wxString name = aGroup->GetName();

        if( name.IsEmpty() )
            return;

        int ptSize = 12;
        int scaledSize = abs( KiROUND( m_gal->GetScreenWorldMatrix().GetScale().x * ptSize ) );
        int unscaledSize = Mils2iu( ptSize );

        // Scale by zoom a bit, but not too much
        int     textSize = ( scaledSize + ( unscaledSize * 2 ) ) / 3;
        int     penWidth = textSize / 10;
        wxPoint textOffset = wxPoint( width.x / 2, - KiROUND( textSize * 0.5 ) );
        wxPoint titleHeight = wxPoint( 0, KiROUND( textSize * 2.0 ) );

        if( PrintableCharCount( name ) * textSize < bbox.GetWidth() )
        {
            m_gal->DrawLine( topLeft, topLeft - titleHeight );
            m_gal->DrawLine( topLeft - titleHeight, topLeft + width - titleHeight );
            m_gal->DrawLine( topLeft + width - titleHeight, topLeft + width );

            m_gal->SetFontBold( false );
            m_gal->SetFontItalic( true );
            m_gal->SetFontUnderlined( false );
            m_gal->SetTextMirrored( m_gal->IsFlippedX() );
            m_gal->SetHorizontalJustify( GR_TEXT_HJUSTIFY_CENTER );
            m_gal->SetVerticalJustify( GR_TEXT_VJUSTIFY_BOTTOM );
            m_gal->SetIsFill( false );
            m_gal->SetGlyphSize( VECTOR2D( textSize, textSize ) );
            m_gal->SetLineWidth( penWidth );
            m_gal->StrokeText( aGroup->GetName(), topLeft + textOffset, 0.0 );
        }
    }
}


void PCB_PAINTER::draw( const ZONE* aZone, int aLayer )
{
    /*
     * aLayer will be the virtual zone layer (LAYER_ZONE_START, ... in GAL_LAYER_ID)
     * This is used for draw ordering in the GAL.
     * The color for the zone comes from the associated copper layer ( aLayer - LAYER_ZONE_START )
     * and the visibility comes from the combination of that copper layer and LAYER_ZONES
     */
    wxASSERT( IsZoneLayer( aLayer ) );
    PCB_LAYER_ID layer = static_cast<PCB_LAYER_ID>( aLayer - LAYER_ZONE_START );

    if( !aZone->IsOnLayer( layer ) )
        return;

    COLOR4D              color = m_pcbSettings.GetColor( aZone, layer );
    std::deque<VECTOR2D> corners;
    ZONE_DISPLAY_MODE    displayMode = m_pcbSettings.m_zoneDisplayMode;

    // Draw the outline
    const SHAPE_POLY_SET* outline = aZone->Outline();

    if( m_pcbSettings.m_zoneOutlines && outline && outline->OutlineCount() > 0 )
    {
        m_gal->SetStrokeColor( color.a > 0.0 ? color.WithAlpha( 1.0 ) : color );
        m_gal->SetIsFill( false );
        m_gal->SetIsStroke( true );
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );

        // Draw each contour (main contour and holes)

        /* This line:
         * m_gal->DrawPolygon( *outline );
         * should be enough, but currently does not work to draw holes contours in a complex polygon
         * so each contour is draw as a simple polygon
         */

        // Draw the main contour
        m_gal->DrawPolyline( outline->COutline( 0 ) );

        // Draw holes
        int holes_count = outline->HoleCount( 0 );

        for( int ii = 0; ii < holes_count; ++ii )
            m_gal->DrawPolyline( outline->CHole( 0, ii ) );

        // Draw hatch lines
        for( const SEG& hatchLine : aZone->GetHatchLines() )
            m_gal->DrawLine( hatchLine.A, hatchLine.B );
    }

    // Draw the filling
    if( displayMode == ZONE_DISPLAY_MODE::SHOW_FILLED
            || displayMode == ZONE_DISPLAY_MODE::SHOW_FRACTURE_BORDERS
            || displayMode == ZONE_DISPLAY_MODE::SHOW_TRIANGULATION )
    {
        const SHAPE_POLY_SET& polySet = aZone->GetFilledPolysList( layer );

        if( polySet.OutlineCount() == 0 )  // Nothing to draw
            return;

        // Set up drawing options
        int outline_thickness = 0;

        if( aZone->GetFilledPolysUseThickness( layer ) )
            outline_thickness = aZone->GetMinThickness();

        m_gal->SetStrokeColor( color );
        m_gal->SetFillColor( color );
        m_gal->SetLineWidth( outline_thickness );

        if( displayMode == ZONE_DISPLAY_MODE::SHOW_FILLED )
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( outline_thickness > 0 );
        }
        else
        {
            m_gal->SetIsFill( false );
            m_gal->SetIsStroke( true );
        }

        m_gal->DrawPolygon( polySet, displayMode == ZONE_DISPLAY_MODE::SHOW_TRIANGULATION );
    }
}


void PCB_PAINTER::draw( const PCB_DIMENSION_BASE* aDimension, int aLayer )
{
    const COLOR4D& strokeColor = m_pcbSettings.GetColor( aDimension, aLayer );

    m_gal->SetStrokeColor( strokeColor );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );

    if( m_pcbSettings.m_sketchGraphics )
    {
        // Outline mode
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    }
    else
    {
        // Filled mode
        m_gal->SetLineWidth( getLineThickness( aDimension->GetLineThickness() ) );
    }

    // Draw dimension shapes
    // TODO(JE) lift this out
    for( const std::shared_ptr<SHAPE>& shape : aDimension->GetShapes() )
    {
        switch( shape->Type() )
        {
        case SH_SEGMENT:
        {
            const SEG& seg = static_cast<const SHAPE_SEGMENT*>( shape.get() )->GetSeg();
            m_gal->DrawLine( seg.A, seg.B );
            break;
        }

        case SH_CIRCLE:
        {
            int radius = static_cast<const SHAPE_CIRCLE*>( shape.get() )->GetRadius();
            m_gal->DrawCircle( shape->Centre(), radius );
            break;
        }

        default:
            break;
        }
    }

    // Draw text
    const PCB_TEXT& text = aDimension->Text();
    VECTOR2D position( text.GetTextPos().x, text.GetTextPos().y );

    if( m_pcbSettings.m_sketchText )
    {
        // Outline mode
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    }
    else
    {
        // Filled mode
        m_gal->SetLineWidth( getLineThickness( text.GetEffectiveTextPenWidth() ) );
    }

    m_gal->SetTextAttributes( &text );
    m_gal->StrokeText( text.GetShownText(), position, text.GetTextAngleRadians() );
}


void PCB_PAINTER::draw( const PCB_TARGET* aTarget )
{
    const COLOR4D& strokeColor = m_pcbSettings.GetColor( aTarget, aTarget->GetLayer() );
    VECTOR2D position( aTarget->GetPosition() );
    double   size, radius;

    m_gal->SetLineWidth( getLineThickness( aTarget->GetWidth() ) );
    m_gal->SetStrokeColor( strokeColor );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );

    m_gal->Save();
    m_gal->Translate( position );

    if( aTarget->GetShape() )
    {
        // shape x
        m_gal->Rotate( M_PI / 4.0 );
        size   = 2.0 * aTarget->GetSize() / 3.0;
        radius = aTarget->GetSize() / 2.0;
    }
    else
    {
        // shape +
        size   = aTarget->GetSize() / 2.0;
        radius = aTarget->GetSize() / 3.0;
    }

    m_gal->DrawLine( VECTOR2D( -size, 0.0 ), VECTOR2D( size, 0.0 ) );
    m_gal->DrawLine( VECTOR2D( 0.0, -size ), VECTOR2D( 0.0,  size ) );
    m_gal->DrawCircle( VECTOR2D( 0.0, 0.0 ), radius );

    m_gal->Restore();
}


void PCB_PAINTER::draw( const PCB_MARKER* aMarker, int aLayer )
{
    bool isShadow = aLayer == LAYER_MARKER_SHADOWS;

    // Don't paint shadows for invisible markers.
    // It would be nice to do this through layer dependencies but we can't do an "or" there today
    if( isShadow && aMarker->GetBoard()
            && !aMarker->GetBoard()->IsElementVisible( aMarker->GetColorLayer() ) )
    {
        return;
    }

    const_cast<PCB_MARKER*>( aMarker )->SetZoom( 1.0 / sqrt( m_gal->GetZoomFactor() ) );

    SHAPE_LINE_CHAIN polygon;
    aMarker->ShapeToPolygon( polygon );

    COLOR4D color = m_pcbSettings.GetColor( aMarker, isShadow ? LAYER_MARKER_SHADOWS
                                                              : aMarker->GetColorLayer() );

    m_gal->Save();
    m_gal->Translate( aMarker->GetPosition() );

    if( isShadow )
    {
        m_gal->SetStrokeColor( color );
        m_gal->SetIsStroke( true );
        m_gal->SetLineWidth( aMarker->MarkerScale() );
    }
    else
    {
        m_gal->SetFillColor( color );
        m_gal->SetIsFill( true );
    }

    m_gal->DrawPolygon( polygon );
    m_gal->Restore();
}


const double PCB_RENDER_SETTINGS::MAX_FONT_SIZE = Millimeter2iu( 10.0 );
