/*
 * This program source code file is part of KiCad, a free EDA CAD application.
 *
 * Copyright (C) 2013-2019 CERN
 * Copyright (C) 2021-2022 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 <fp_textbox.h>
#include <pad.h>
#include <pcb_shape.h>
#include <string_utils.h>
#include <zone.h>
#include <pcb_text.h>
#include <pcb_textbox.h>
#include <pcb_marker.h>
#include <pcb_dimension.h>
#include <pcb_target.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 <settings/common_settings.h>
#include <pcbnew_settings.h>

#include <convert_basic_shapes_to_polygon.h>
#include <gal/graphics_abstraction_layer.h>
#include <callback_gal.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>
#include <kiface_base.h>
#include <gr_text.h>
#include <pgm_base.h>

using namespace KIGFX;

PCB_RENDER_SETTINGS::PCB_RENDER_SETTINGS()
{
    m_backgroundColor = COLOR4D( 0.0, 0.0, 0.0, 1.0 );
    m_ZoneDisplayMode = ZONE_DISPLAY_MODE::SHOW_FILLED;
    m_netColorMode = NET_COLOR_MODE::RATSNEST;
    m_ContrastModeDisplay = HIGH_CONTRAST_MODE::NORMAL;
    m_DrawIndividualViaLayers = false;

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

    m_ForcePadSketchModeOff    = false;
    m_ForcePadSketchModeOn     = false;

    SetDashLengthRatio( 12 );       // From ISO 128-2
    SetGapLengthRatio( 3 );         // From ISO 128-2

    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( 1.0, 1.0, 1.0, 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;
    }

    if( PgmOrNull() )   // can be null if used without project (i.e. from python script)
        m_hiContrastFactor = 1.0f - Pgm().GetCommonSettings()->m_Appearance.hicontrast_dimming_factor;
    else
        m_hiContrastFactor = 1.0f - 0.8f;   // default value

    update();
}


void PCB_RENDER_SETTINGS::LoadDisplayOptions( const PCB_DISPLAY_OPTIONS& aOptions )
{
    m_hiContrastEnabled   = aOptions.m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL;
    m_ZoneDisplayMode     = aOptions.m_ZoneDisplayMode;
    m_ContrastModeDisplay = aOptions.m_ContrastModeDisplay;
    m_netColorMode        = aOptions.m_NetColorMode;

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


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;
}


PCBNEW_SETTINGS* pcbconfig()
{
    return dynamic_cast<PCBNEW_SETTINGS*>( Kiface().KifaceSettings() );
}


bool PCB_RENDER_SETTINGS::GetShowPageLimits() const
{
    return pcbconfig() && pcbconfig()->m_ShowPageLimits;
}


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();

        if( item->GetParentFootprint() && !board->IsFootprintHolder() )
        {
            FOOTPRINT* parentFP = static_cast<FOOTPRINT*>( item->GetParentFootprint() );

            if( item->GetLayerSet().count() > 1 )
            {
                // For multi-layer objects, exclude only those layers that are private
                if( IsPcbLayer( aLayer ) && parentFP->GetPrivateLayers().test( aLayer ) )
                    return false;
            }
            else if( item->GetLayerSet().count() == 1 )
            {
                // For single-layer objects, exclude all layers including ancillary layers
                // such as holes, netnames, etc.
                PCB_LAYER_ID singleLayer = item->GetLayerSet().Seq()[0];

                if( parentFP->GetPrivateLayers().test( singleLayer ) )
                    return false;
            }
        }
    }
    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_TEXTBOX_T:
        draw( static_cast<const PCB_TEXTBOX*>( item ), aLayer );
        break;

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

    case PCB_FP_TEXTBOX_T:
        draw( static_cast<const FP_TEXTBOX*>( 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:
    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_RADIAL_T:
    case PCB_DIM_ORTHOGONAL_T:
    case PCB_DIM_LEADER_T:
    case PCB_FP_DIM_ALIGNED_T:
    case PCB_FP_DIM_CENTER_T:
    case PCB_FP_DIM_RADIAL_T:
    case PCB_FP_DIM_ORTHOGONAL_T:
    case PCB_FP_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( m_pcbSettings.GetDrawBoundingBoxes() )
    {
        // 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 )
            {
                const SHAPE_POLY_SET& convex = fp->GetBoundingHull();

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

    return true;
}


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

    if( IsNetnameLayer( aLayer ) )
    {
        if( !pcbconfig() || pcbconfig()->m_Display.m_NetNames < 2 )
            return;

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

        // When drawing netnames, clip the track to the viewport
        BOX2D             viewport;
        VECTOR2D          screenSize = m_gal->GetScreenPixelSize();
        const MATRIX3x3D& matrix = m_gal->GetScreenWorldMatrix();

        viewport.SetOrigin( VECTOR2D( matrix * VECTOR2D( 0, 0 ) ) );
        viewport.SetEnd( VECTOR2D( matrix * screenSize ) );

        EDA_RECT clipBox( viewport.Normalize() );
        SEG      visibleSeg( start, end );

        ClipLine( &clipBox, visibleSeg.A.x, visibleSeg.A.y, visibleSeg.B.x, visibleSeg.B.y );

        // Check if the track is long enough to have a netname displayed
        int seg_minlenght = track_width * 6;    // min lenght of the visible segment to draw the net name

        if( visibleSeg.Length() < seg_minlenght )
            return;

        const     wxString& netName = UnescapeString( aTrack->GetShortNetname() );
        double    textSize = track_width;
        double    penWidth = textSize / 12.0;
        VECTOR2D  textPosition = ( visibleSeg.A + visibleSeg.B ) / 2.0;  // center of the track
        EDA_ANGLE textOrientation;

        // If the last position is still on the track, and it's some reasonable distance inside
        // the viewport then don't move the netname; just use the last position.
        if( visibleSeg.Distance( aTrack->m_LastNetnamePosition ) < penWidth
                && clipBox.Inflate( -seg_minlenght ).Contains( aTrack->m_LastNetnamePosition ) )
        {
            textPosition = aTrack->m_LastNetnamePosition;
        }
        else
        {
            aTrack->m_LastNetnamePosition = textPosition;
        }

        if( end.y == start.y ) // horizontal
        {
            textOrientation = ANGLE_HORIZONTAL;
        }
        else if( end.x == start.x ) // vertical
        {
            textOrientation = ANGLE_VERTICAL;
        }
        else
        {
            textOrientation = EDA_ANGLE( visibleSeg.B - visibleSeg.A ) + ANGLE_90;
            textOrientation.Normalize90();
        }

        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_H_ALIGN_CENTER );
        m_gal->SetVerticalJustify( GR_TEXT_V_ALIGN_CENTER );
        m_gal->BitmapText( netName, textPosition, textOrientation );

        return;
    }
    else if( IsCopperLayer( aLayer ) || aLayer == LAYER_LOCKED_ITEM_SHADOW )
    {
        // Draw a regular track
        bool outline_mode = pcbconfig()
                            && !pcbconfig()->m_Display.m_DisplayPcbTrackFill
                            && aLayer != LAYER_LOCKED_ITEM_SHADOW;
        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 );

        if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
            track_width = track_width * 1.5;

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

    // Clearance lines
    if( pcbconfig() && pcbconfig()->m_Display.m_TrackClearance == SHOW_WITH_VIA_ALWAYS
            && !m_pcbSettings.m_isPrinting && aLayer != LAYER_LOCKED_ITEM_SHADOW )
    {
        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, track_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();
    EDA_ANGLE start_angle = aArc->GetArcAngleStart();
    EDA_ANGLE angle = aArc->GetAngle();

    if( IsNetnameLayer( aLayer ) )
    {
        // Ummm, yeah.  Anyone fancy implementing text on a path?
        return;
    }
    else if( IsCopperLayer( aLayer ) || aLayer == LAYER_LOCKED_ITEM_SHADOW )
    {
        // Draw a regular track
        bool outline_mode = pcbconfig()
                            && !pcbconfig()->m_Display.m_DisplayPcbTrackFill
                            && aLayer != LAYER_LOCKED_ITEM_SHADOW;
        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 );

        if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
            width = width * 1.5;

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

    // Clearance lines
    if( pcbconfig() && pcbconfig()->m_Display.m_TrackClearance == SHOW_WITH_VIA_ALWAYS
            && !m_pcbSettings.m_isPrinting && aLayer != LAYER_LOCKED_ITEM_SHADOW )
    {
        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( !pcbconfig() )
            return;

        if( pcbconfig()->m_Display.m_NetNames == 0
                || pcbconfig()->m_Display.m_NetNames == 2
                || 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.75;
        VECTOR2D namesize( tsize, tsize );

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

        m_gal->Restore();

        return;
    }

    bool outline_mode = pcbconfig() && !pcbconfig()->m_Display.m_DisplayViaFill;

    m_gal->SetStrokeColor( color );
    m_gal->SetFillColor( color );
    m_gal->SetIsStroke( true );
    m_gal->SetIsFill( false );

    if( outline_mode )
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );

    if( aLayer == LAYER_VIA_HOLEWALLS )
    {
        double radius = ( getDrillSize( aVia ) / 2.0 ) + m_holePlatingThickness;

        if( !outline_mode )
        {
            m_gal->SetLineWidth( m_holePlatingThickness );
            radius -= m_holePlatingThickness / 2.0;
        }

        m_gal->DrawCircle( center, radius );
    }
    else if( aLayer == LAYER_VIA_HOLES )
    {
        m_gal->SetIsStroke( false );
        m_gal->SetIsFill( true );
        m_gal->DrawCircle( center, getDrillSize( aVia ) / 2.0 );
    }
    else if( aLayer == LAYER_VIA_THROUGH || m_pcbSettings.m_DrawIndividualViaLayers )
    {
        int    annular_width = ( aVia->GetWidth() - getDrillSize( aVia ) ) / 2.0;
        double radius = aVia->GetWidth() / 2.0;

        if( !outline_mode )
        {
            m_gal->SetLineWidth( annular_width );
            radius -= annular_width / 2.0;
        }

        m_gal->DrawCircle( center, radius );
    }
    else if( aLayer == LAYER_VIA_BBLIND || aLayer == LAYER_VIA_MICROVIA )
    {
        int    annular_width = ( aVia->GetWidth() - getDrillSize( aVia ) ) / 2.0;
        double radius = aVia->GetWidth() / 2.0;

        // 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 );

        if( !outline_mode )
        {
            m_gal->SetIsStroke( false );
            m_gal->SetIsFill( true );
        }

        m_gal->SetStrokeColor( m_pcbSettings.GetColor( aVia, layerTop ) );
        m_gal->SetFillColor( m_pcbSettings.GetColor( aVia, layerTop ) );
        m_gal->DrawArc( center, radius, EDA_ANGLE( 240, DEGREES_T ), EDA_ANGLE( 300, DEGREES_T ) );

        m_gal->SetStrokeColor( m_pcbSettings.GetColor( aVia, layerBottom ) );
        m_gal->SetFillColor( m_pcbSettings.GetColor( aVia, layerBottom ) );
        m_gal->DrawArc( center, radius, EDA_ANGLE( 60, DEGREES_T ), EDA_ANGLE( 120, DEGREES_T ) );

        m_gal->SetStrokeColor( color );
        m_gal->SetFillColor( color );
        m_gal->SetIsStroke( true );
        m_gal->SetIsFill( false );

        if( !outline_mode )
        {
            m_gal->SetLineWidth( annular_width );
            radius -= annular_width / 2.0;
        }

        m_gal->DrawCircle( center, radius );
    }
    else if( aLayer == LAYER_LOCKED_ITEM_SHADOW )    // draw a ring around the via
    {
        int ring_width = aVia->GetWidth() * 0.2;
        m_gal->SetLineWidth( ring_width );

        m_gal->DrawCircle( center, ( aVia->GetWidth() + ring_width ) / 2.0 );
    }

    // Clearance lines
    if( pcbconfig() && pcbconfig()->m_Display.m_TrackClearance == SHOW_WITH_VIA_ALWAYS
            && aLayer != LAYER_VIA_HOLES
            && !m_pcbSettings.m_isPrinting )
    {
        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 ) )
    {
        if( !pcbconfig() )
            return;

        PCBNEW_SETTINGS::DISPLAY_OPTIONS& displayOpts = pcbconfig()->m_Display;
        wxString                          netname;
        wxString                          padNumber;

        if( displayOpts.m_PadNumbers )
            padNumber = UnescapeString( aPad->GetNumber() );

        if( displayOpts.m_NetNames == 1 || displayOpts.m_NetNames == 3 )
            netname = UnescapeString( aPad->GetShortNetname() );

        if( displayOpts.m_PadNoConnects
                && aPad->GetShortNetname().StartsWith( wxT( "unconnected-(" ) ) )
        {
            wxString pinType = aPad->GetPinType();

            if( pinType == wxT( "no_connect" ) || pinType.EndsWith( wxT( "+no_connect" ) ) )
                netname = wxT( "x" );
            else if( pinType == wxT( "free" ) )
                netname = wxT( "*" );
        }

        if( netname.IsEmpty() && padNumber.IsEmpty() )
            return;

        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( -ANGLE_90.AsRadians() );
            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_H_ALIGN_CENTER );
        m_gal->SetVerticalJustify( GR_TEXT_V_ALIGN_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
        VECTOR2I textpos( 0, 0 );

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

        if( !netname.IsEmpty() )
        {
            // 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.75;
            VECTOR2D namesize( tsize, tsize );

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

        if( !padNumber.IsEmpty() )
        {
            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.75;
            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, ANGLE_HORIZONTAL );
        }

        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;
    }

    bool outline_mode = pcbconfig() && !pcbconfig()->m_Display.m_DisplayPadFill;

    if( m_pcbSettings.m_ForcePadSketchModeOff )
        outline_mode = false;
    else if( m_pcbSettings.m_ForcePadSketchModeOn )
        outline_mode = true;

    if( outline_mode )
    {
        // 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
    {
        VECTOR2I pad_size = aPad->GetSize();
        VECTOR2I margin;

        switch( aLayer )
        {
        case F_Mask:
        case B_Mask:
            margin.x = margin.y = aPad->GetSolderMaskExpansion();
            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 = !outline_mode;

        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, FULL_CIRCLE );
                        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 );
        }
    }

    if( pcbconfig() && pcbconfig()->m_Display.m_PadClearance
            && ( aLayer == LAYER_PAD_FR || aLayer == LAYER_PAD_BK || aLayer == LAYER_PADS_TH )
            && !m_pcbSettings.m_isPrinting )
    {
        /* 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 )
        {
            if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
                color = m_pcbSettings.GetLayerColor( LAYER_NON_PLATEDHOLES );

            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 )
{
    COLOR4D        color = m_pcbSettings.GetColor( aShape, aShape->GetLayer() );
    bool           outline_mode = pcbconfig() && !pcbconfig()->m_Display.m_DisplayGraphicsFill;
    int            thickness = getLineThickness( aShape->GetWidth() );
    PLOT_DASH_TYPE lineStyle = aShape->GetStroke().GetPlotStyle();

    if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
    {
        color = m_pcbSettings.GetColor( aShape, aLayer );
        thickness = std::max( thickness * 3, Millimeter2iu( 0.2 ) );
    }

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

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

    if( lineStyle <= PLOT_DASH_TYPE::FIRST_TYPE )
    {
        switch( aShape->GetShape() )
        {
        case SHAPE_T::SEGMENT:
            if( outline_mode )
            {
                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<VECTOR2I> pts = aShape->GetRectCorners();

            if( outline_mode )
            {
                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 VECTOR2I& pt : pts )
                        poly.Append( pt );

                    m_gal->DrawPolygon( poly );
                }
            }

            break;
        }

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

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

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

        case SHAPE_T::CIRCLE:
            if( outline_mode )
            {
                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->GetOrientation().AsRadians() );
            }

            if( outline_mode )
            {
                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( outline_mode )
            {
                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;
        }
    }
    else
    {
        if( !outline_mode )
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( false );
        }

        std::vector<SHAPE*> shapes = aShape->MakeEffectiveShapes( true );

        for( SHAPE* shape : shapes )
        {
            STROKE_PARAMS::Stroke( shape, lineStyle, thickness, &m_pcbSettings,
                                   [&]( const VECTOR2I& a, const VECTOR2I& b )
                                   {
                                       m_gal->DrawSegment( a, b, thickness );
                                   } );
        }

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


void PCB_PAINTER::strokeText( const wxString& aText, const VECTOR2I& aPosition,
                              const TEXT_ATTRIBUTES& aAttrs )
{
    KIFONT::FONT* font = aAttrs.m_Font;

    if( !font )
        font = KIFONT::FONT::GetFont( wxEmptyString, aAttrs.m_Bold, aAttrs.m_Italic );

    m_gal->SetIsFill( font->IsOutline() );
    m_gal->SetIsStroke( font->IsStroke() );

    font->Draw( m_gal, aText, aPosition, aAttrs );
}


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

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

    if( aLayer == LAYER_LOCKED_ITEM_SHADOW )    // happens only if locked
    {
        const COLOR4D color = m_pcbSettings.GetColor( aText, aLayer );

        m_gal->SetIsFill( true );
        m_gal->SetIsStroke( false );
        m_gal->SetFillColor( color );

        SHAPE_POLY_SET poly;
        aText->TransformShapeWithClearanceToPolygon( poly, aText->GetLayer(), 0, m_maxError,
                                                     ERROR_OUTSIDE );
        m_gal->DrawPolygon( poly );

        return;
    }

    TEXT_ATTRIBUTES attrs = aText->GetAttributes();
    const COLOR4D& color = m_pcbSettings.GetColor( aText, aText->GetLayer() );
    bool           outline_mode = pcbconfig() && !pcbconfig()->m_Display.m_DisplayTextFill;

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

    if( aText->IsKnockout() )
    {
        KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
        SHAPE_POLY_SET             knockouts;

        CALLBACK_GAL callback_gal( empty_opts,
                // Polygon callback
                [&]( const SHAPE_LINE_CHAIN& aPoly )
                {
                    knockouts.AddOutline( aPoly );
                } );

        KIFONT::FONT* font = aText->GetDrawFont();

        attrs.m_StrokeWidth = getLineThickness( aText->GetEffectiveTextPenWidth() );

        callback_gal.SetIsFill( font->IsOutline() );
        callback_gal.SetIsStroke( font->IsStroke() );
        callback_gal.SetLineWidth( attrs.m_StrokeWidth );
        font->Draw( &callback_gal, resolvedText, aText->GetDrawPos(), attrs );

        SHAPE_POLY_SET finalPoly;
        int            margin = attrs.m_StrokeWidth * 1.5;

        aText->TransformBoundingBoxWithClearanceToPolygon( &finalPoly, margin );
        finalPoly.BooleanSubtract( knockouts, SHAPE_POLY_SET::PM_FAST );
        finalPoly.Fracture( SHAPE_POLY_SET::PM_FAST );

        m_gal->SetIsStroke( false );
        m_gal->SetIsFill( true );
        m_gal->DrawPolygon( finalPoly );
    }
    else
    {
        if( outline_mode )
            attrs.m_StrokeWidth = m_pcbSettings.m_outlineWidth;
        else
            attrs.m_StrokeWidth = getLineThickness( aText->GetEffectiveTextPenWidth() );

        std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = aText->GetRenderCache( resolvedText );

        if( cache )
        {
            for( const std::unique_ptr<KIFONT::GLYPH>& glyph : *cache )
                m_gal->DrawGlyph( *glyph.get() );
        }
        else
        {
            strokeText( resolvedText, aText->GetTextPos(), attrs );
        }
    }
}


void PCB_PAINTER::draw( const PCB_TEXTBOX* aTextBox, int aLayer )
{
    const COLOR4D& color = m_pcbSettings.GetColor( aTextBox, aLayer );
    int            thickness = getLineThickness( aTextBox->GetWidth() );
    PLOT_DASH_TYPE lineStyle = aTextBox->GetStroke().GetPlotStyle();
    wxString       resolvedText( aTextBox->GetShownText() );

    if( aLayer == LAYER_LOCKED_ITEM_SHADOW )    // happens only if locked
    {
        const COLOR4D sh_color = m_pcbSettings.GetColor( aTextBox, aLayer );

        m_gal->SetIsFill( true );
        m_gal->SetIsStroke( false );
        m_gal->SetFillColor( sh_color );
        m_gal->SetStrokeColor( sh_color );

        // Draw the box with a larger thickness than box thickness to show
        // the shadow mask
        std::vector<VECTOR2I> pts = aTextBox->GetCorners();
        int line_thickness = std::max( thickness*3, Millimeter2iu( 0.2 ) );

        for( size_t ii = 0; ii < pts.size(); ++ii )
            m_gal->DrawSegment( pts[ ii ], pts[ (ii + 1) % pts.size() ], line_thickness );
    }

    m_gal->SetFillColor( color );
    m_gal->SetStrokeColor( color );
    m_gal->SetIsFill( true );
    m_gal->SetIsStroke( false );

    if( lineStyle <= PLOT_DASH_TYPE::FIRST_TYPE )
    {
        if( thickness > 0 )
        {
            std::vector<VECTOR2I> pts = aTextBox->GetCorners();

            for( size_t ii = 0; ii < pts.size(); ++ii )
                m_gal->DrawSegment( pts[ ii ], pts[ (ii + 1) % pts.size() ], thickness );
        }
    }
    else
    {
        std::vector<SHAPE*> shapes = aTextBox->MakeEffectiveShapes( true );

        for( SHAPE* shape : shapes )
        {
            STROKE_PARAMS::Stroke( shape, lineStyle, thickness, &m_pcbSettings,
                                   [&]( const VECTOR2I& a, const VECTOR2I& b )
                                   {
                                       m_gal->DrawSegment( a, b, thickness );
                                   } );
        }

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

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

    TEXT_ATTRIBUTES attrs = aTextBox->GetAttributes();
    attrs.m_StrokeWidth = getLineThickness( aTextBox->GetEffectiveTextPenWidth() );

    std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = aTextBox->GetRenderCache( resolvedText );

    if( aLayer == LAYER_LOCKED_ITEM_SHADOW )
    {
        const COLOR4D sh_color = m_pcbSettings.GetColor( aTextBox, aLayer );
        m_gal->SetFillColor( sh_color );
        m_gal->SetStrokeColor( sh_color );
        attrs.m_StrokeWidth *= 3;
    }

    if( cache )
    {
        for( const std::unique_ptr<KIFONT::GLYPH>& glyph : *cache )
            m_gal->DrawGlyph( *glyph.get() );
    }
    else
    {
        strokeText( resolvedText, aTextBox->GetDrawPos(), attrs );
    }
}


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

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

    const COLOR4D&  color = m_pcbSettings.GetColor( aText, aLayer );
    bool            outline_mode = pcbconfig() && !pcbconfig()->m_Display.m_DisplayTextFill;
    TEXT_ATTRIBUTES attrs = aText->GetAttributes();

    m_gal->SetStrokeColor( color );
    m_gal->SetFillColor( color );
    attrs.m_Angle = aText->GetDrawRotation();

    if( aText->IsKnockout() )
    {
        KIGFX::GAL_DISPLAY_OPTIONS empty_opts;
        SHAPE_POLY_SET             knockouts;

        CALLBACK_GAL callback_gal( empty_opts,
                // Polygon callback
                [&]( const SHAPE_LINE_CHAIN& aPoly )
                {
                    knockouts.AddOutline( aPoly );
                } );

        KIFONT::FONT* font = aText->GetDrawFont();

        attrs.m_StrokeWidth = getLineThickness( aText->GetEffectiveTextPenWidth() );

        callback_gal.SetIsFill( font->IsOutline() );
        callback_gal.SetIsStroke( font->IsStroke() );
        callback_gal.SetLineWidth( attrs.m_StrokeWidth );
        font->Draw( &callback_gal, resolvedText, aText->GetDrawPos(), attrs );

        SHAPE_POLY_SET finalPoly;
        int            margin = attrs.m_StrokeWidth * 1.5;

        aText->TransformBoundingBoxWithClearanceToPolygon( &finalPoly, margin );
        finalPoly.BooleanSubtract( knockouts, SHAPE_POLY_SET::PM_FAST );
        finalPoly.Fracture( SHAPE_POLY_SET::PM_FAST );

        m_gal->SetIsStroke( false );
        m_gal->SetIsFill( true );
        m_gal->DrawPolygon( finalPoly );
    }
    else
    {
        if( outline_mode )
            attrs.m_StrokeWidth = m_pcbSettings.m_outlineWidth;
        else
            attrs.m_StrokeWidth = getLineThickness( aText->GetEffectiveTextPenWidth() );

        std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = aText->GetRenderCache( resolvedText );

        if( cache )
        {
            for( const std::unique_ptr<KIFONT::GLYPH>& glyph : *cache )
                m_gal->DrawGlyph( *glyph.get() );
        }
        else
        {
            strokeText( resolvedText, aText->GetTextPos(), attrs );
        }
    }

    // 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( aText->GetTextPos(), aText->GetParent()->GetPosition() );
    }
}


void PCB_PAINTER::draw( const FP_TEXTBOX* aTextBox, int aLayer )
{
    const COLOR4D& color = m_pcbSettings.GetColor( aTextBox, aTextBox->GetLayer() );
    int            thickness = getLineThickness( aTextBox->GetWidth() );
    PLOT_DASH_TYPE lineStyle = aTextBox->GetStroke().GetPlotStyle();

    m_gal->SetFillColor( color );
    m_gal->SetStrokeColor( color );
    m_gal->SetIsFill( true );
    m_gal->SetIsStroke( false );

    if( lineStyle <= PLOT_DASH_TYPE::FIRST_TYPE )
    {
        if( thickness > 0 )
        {
            std::vector<VECTOR2I> pts = aTextBox->GetCorners();

            for( size_t ii = 0; ii < pts.size(); ++ii )
                m_gal->DrawSegment( pts[ ii ], pts[ (ii + 1) % pts.size() ], thickness );
        }
    }
    else
    {
        std::vector<SHAPE*> shapes = aTextBox->MakeEffectiveShapes( true );

        for( SHAPE* shape : shapes )
        {
            STROKE_PARAMS::Stroke( shape, lineStyle, thickness, &m_pcbSettings,
                                   [&]( const VECTOR2I& a, const VECTOR2I& b )
                                   {
                                       m_gal->DrawSegment( a, b, thickness );
                                   } );
        }

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

    wxString resolvedText( aTextBox->GetShownText() );

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

    TEXT_ATTRIBUTES attrs = aTextBox->GetAttributes();
    attrs.m_Angle = aTextBox->GetDrawRotation();
    attrs.m_StrokeWidth = getLineThickness( aTextBox->GetEffectiveTextPenWidth() );

    std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = aTextBox->GetRenderCache( resolvedText );

    if( cache )
    {
        for( const std::unique_ptr<KIFONT::GLYPH>& glyph : *cache )
            m_gal->DrawGlyph( *glyph.get() );
    }
    else
    {
        strokeText( resolvedText, aTextBox->GetDrawPos(), attrs );
    }
}


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 ) );
    }

    if( aLayer == LAYER_LOCKED_ITEM_SHADOW )    // happens only if locked
    {
        const COLOR4D color = m_pcbSettings.GetColor( aFootprint, aLayer );

        m_gal->SetIsFill( true );
        m_gal->SetIsStroke( false );
        m_gal->SetFillColor( color );

        const SHAPE_POLY_SET& poly = aFootprint->GetBoundingHull();
        m_gal->DrawPolygon( poly );
    }
}


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 );
        VECTOR2I topLeft = bbox.GetPosition();
        VECTOR2I width = VECTOR2I( bbox.GetWidth(), 0 );
        VECTOR2I height = VECTOR2I( 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;
        VECTOR2I textOffset = VECTOR2I( width.x / 2, -KiROUND( textSize * 0.5 ) );
        VECTOR2I titleHeight = VECTOR2I( 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 );

            TEXT_ATTRIBUTES attrs;
            attrs.m_Italic = true;
            attrs.m_Halign = GR_TEXT_H_ALIGN_CENTER;
            attrs.m_Valign = GR_TEXT_V_ALIGN_BOTTOM;
            attrs.m_Size = VECTOR2I( textSize, textSize );
            attrs.m_StrokeWidth = GetPenSizeForNormal( textSize );

            KIFONT::FONT::GetFont()->Draw( m_gal, aGroup->GetName(), topLeft + textOffset, attrs );
        }
    }
}


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_isPrinting && 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)

        /*
         * 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 std::shared_ptr<SHAPE_POLY_SET>& polySet = aZone->GetFilledPolysList( layer );

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

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

        if( displayMode == ZONE_DISPLAY_MODE::SHOW_FILLED )
        {
            m_gal->SetIsFill( true );
            m_gal->SetIsStroke( false );
        }
        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& color = m_pcbSettings.GetColor( aDimension, aLayer );

    m_gal->SetStrokeColor( color );
    m_gal->SetFillColor( color );
    m_gal->SetIsFill( false );
    m_gal->SetIsStroke( true );

    bool outline_mode = pcbconfig() && !pcbconfig()->m_Display.m_DisplayGraphicsFill;

    if( outline_mode )
        m_gal->SetLineWidth( m_pcbSettings.m_outlineWidth );
    else
        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();
    wxString        resolvedText = text.GetShownText();
    TEXT_ATTRIBUTES attrs = text.GetAttributes();

    if( outline_mode )
        attrs.m_StrokeWidth = m_pcbSettings.m_outlineWidth;
    else
        attrs.m_StrokeWidth = getLineThickness( text.GetEffectiveTextPenWidth() );

    std::vector<std::unique_ptr<KIFONT::GLYPH>>* cache = text.GetRenderCache( resolvedText );

    if( cache )
    {
        for( const std::unique_ptr<KIFONT::GLYPH>& glyph : *cache )
            m_gal->DrawGlyph( *glyph.get() );
    }
    else
    {
        strokeText( resolvedText, text.GetTextPos(), attrs );
    }
}


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 );
