From 93ea523eec5c9755c4359f054ea7f0eb868dc61c Mon Sep 17 00:00:00 2001
From: Jeff Young <jeff@rokeby.ie>
Date: Thu, 13 Mar 2025 12:26:17 +0000
Subject: [PATCH] De-duplicate table border drawing code.

Also cleans up a misconception about table header borders,
and renames the getter/setter to be clearer.

Also makes sure that table cells are updated when the table
layer changes.

And another bug where we were writing the grey color value
back to the cell for hidden cells.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/20319
---
 common/hash_eda.cpp                           |   2 +-
 eeschema/dialogs/dialog_table_properties.cpp  |  14 +-
 .../sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp |   4 +-
 .../kicad_sexpr/sch_io_kicad_sexpr_parser.cpp |   2 +-
 eeschema/sch_painter.cpp                      |   2 +-
 eeschema/sch_table.cpp                        |  10 +-
 eeschema/sch_table.h                          |   6 +-
 pcbnew/dialogs/dialog_table_properties.cpp    |  15 +-
 .../pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp |   4 +-
 .../kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp |   2 +-
 pcbnew/pcb_painter.cpp                        | 119 +++-------------
 pcbnew/pcb_table.cpp                          | 134 ++++++++++--------
 pcbnew/pcb_table.h                            |  10 +-
 pcbnew/plot_brditems_plotter.cpp              | 112 ++-------------
 14 files changed, 147 insertions(+), 289 deletions(-)

diff --git a/common/hash_eda.cpp b/common/hash_eda.cpp
index 6f500e9150..570e102496 100644
--- a/common/hash_eda.cpp
+++ b/common/hash_eda.cpp
@@ -323,7 +323,7 @@ size_t hash_fp_item( const EDA_ITEM* aItem, int aFlags )
         ret = hash_board_item( table, aFlags );
 
         hash_combine( ret, table->StrokeExternal() );
-        hash_combine( ret, table->StrokeHeader() );
+        hash_combine( ret, table->StrokeHeaderSeparator() );
         hash_combine( ret, table->StrokeColumns() );
         hash_combine( ret, table->StrokeRows() );
 
diff --git a/eeschema/dialogs/dialog_table_properties.cpp b/eeschema/dialogs/dialog_table_properties.cpp
index 05d040565f..c3cb1f4df5 100644
--- a/eeschema/dialogs/dialog_table_properties.cpp
+++ b/eeschema/dialogs/dialog_table_properties.cpp
@@ -188,7 +188,7 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataToWindow()
     //
 
     m_borderCheckbox->SetValue( m_table->StrokeExternal() );
-    m_headerBorder->SetValue( m_table->StrokeHeader() );
+    m_headerBorder->SetValue( m_table->StrokeHeaderSeparator() );
 
     if( m_table->GetBorderStroke().GetWidth() >= 0 )
         m_borderWidth.SetValue( m_table->GetBorderStroke().GetWidth() );
@@ -202,11 +202,11 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataToWindow()
     else
         m_borderStyleCombo->SetSelection( 0 );
 
-    m_borderWidth.Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
-    m_borderColorLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
-    m_borderColorSwatch->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
-    m_borderStyleLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
-    m_borderStyleCombo->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
+    m_borderWidth.Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
+    m_borderColorLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
+    m_borderColorSwatch->Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
+    m_borderStyleLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
+    m_borderStyleCombo->Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
 
     bool rows = m_table->StrokeRows() && m_table->GetSeparatorsStroke().GetWidth() >= 0;
     bool cols = m_table->StrokeColumns() && m_table->GetSeparatorsStroke().GetWidth() >= 0;
@@ -344,7 +344,7 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataFromWindow()
     }
 
     m_table->SetStrokeExternal( m_borderCheckbox->GetValue() );
-    m_table->SetStrokeHeader( m_headerBorder->GetValue() );
+    m_table->SetStrokeHeaderSeparator( m_headerBorder->GetValue() );
     {
         STROKE_PARAMS stroke = m_table->GetBorderStroke();
 
diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp
index 794a39a5cd..814229c6b5 100644
--- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp
+++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr.cpp
@@ -1392,9 +1392,9 @@ void SCH_IO_KICAD_SEXPR::saveTable( SCH_TABLE* aTable )
 
     m_out->Print( "(border" );
     KICAD_FORMAT::FormatBool( m_out, "external", aTable->StrokeExternal() );
-    KICAD_FORMAT::FormatBool( m_out, "header", aTable->StrokeHeader() );
+    KICAD_FORMAT::FormatBool( m_out, "header", aTable->StrokeHeaderSeparator() );
 
-    if( aTable->StrokeExternal() || aTable->StrokeHeader() )
+    if( aTable->StrokeExternal() || aTable->StrokeHeaderSeparator() )
         aTable->GetBorderStroke().Format( m_out, schIUScale );
 
     m_out->Print( ")" );               // Close `border` token.
diff --git a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp
index dc1f8b37de..9fdccdd540 100644
--- a/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp
+++ b/eeschema/sch_io/kicad_sexpr/sch_io_kicad_sexpr_parser.cpp
@@ -4607,7 +4607,7 @@ SCH_TABLE* SCH_IO_KICAD_SEXPR_PARSER::parseSchTable()
                     break;
 
                 case T_header:
-                    table->SetStrokeHeader( parseBool() );
+                    table->SetStrokeHeaderSeparator( parseBool() );
                     NeedRIGHT();
                     break;
 
diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp
index ec6092f31c..92344ff29f 100644
--- a/eeschema/sch_painter.cpp
+++ b/eeschema/sch_painter.cpp
@@ -2199,7 +2199,7 @@ void SCH_PAINTER::draw( const SCH_TABLE* aTable, int aLayer, bool aDimmed )
 
         setupStroke( aTable->GetBorderStroke() );
 
-        if( aTable->StrokeHeader() )
+        if( aTable->StrokeHeaderSeparator() )
         {
             if( !first->GetTextAngle().IsHorizontal() )
                 strokeLine( VECTOR2I( first->GetEndX(), pos.y ),
diff --git a/eeschema/sch_table.cpp b/eeschema/sch_table.cpp
index 6524a2f6f3..2914f49845 100644
--- a/eeschema/sch_table.cpp
+++ b/eeschema/sch_table.cpp
@@ -38,7 +38,7 @@
 SCH_TABLE::SCH_TABLE( int aLineWidth ) :
         SCH_ITEM( nullptr, SCH_TABLE_T ),
         m_strokeExternal( true ),
-        m_strokeHeader( true ),
+        m_StrokeHeaderSeparator( true ),
         m_borderStroke( aLineWidth, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ),
         m_strokeRows( true ),
         m_strokeColumns( true ),
@@ -53,7 +53,7 @@ SCH_TABLE::SCH_TABLE( const SCH_TABLE& aTable ) :
         SCH_ITEM( aTable )
 {
     m_strokeExternal = aTable.m_strokeExternal;
-    m_strokeHeader = aTable.m_strokeHeader;
+    m_StrokeHeaderSeparator = aTable.m_StrokeHeaderSeparator;
     m_borderStroke = aTable.m_borderStroke;
     m_strokeRows = aTable.m_strokeRows;
     m_strokeColumns = aTable.m_strokeColumns;
@@ -86,7 +86,7 @@ void SCH_TABLE::SwapData( SCH_ITEM* aItem )
     SCH_TABLE* table = static_cast<SCH_TABLE*>( aItem );
 
     std::swap( m_strokeExternal, table->m_strokeExternal );
-    std::swap( m_strokeHeader, table->m_strokeHeader );
+    std::swap( m_StrokeHeaderSeparator, table->m_StrokeHeaderSeparator );
     std::swap( m_borderStroke, table->m_borderStroke );
     std::swap( m_strokeRows, table->m_strokeRows );
     std::swap( m_strokeColumns, table->m_strokeColumns );
@@ -425,7 +425,7 @@ void SCH_TABLE::Plot( PLOTTER* aPlotter, bool aBackground, const SCH_PLOT_OPTS&
         setupStroke( GetBorderStroke() );
         SCH_TABLECELL* cell = GetCell( 0, 0 );
 
-        if( StrokeHeader() )
+        if( StrokeHeaderSeparator() )
         {
             if( !cell->GetTextAngle().IsHorizontal() )
             {
@@ -526,7 +526,7 @@ static struct SCH_TABLE_DESC
                     tableProps );
 
         propMgr.AddProperty( new PROPERTY<SCH_TABLE, bool>( _HKI( "Header Border" ),
-                    &SCH_TABLE::SetStrokeHeader, &SCH_TABLE::StrokeHeader ),
+                    &SCH_TABLE::SetStrokeHeaderSeparator, &SCH_TABLE::StrokeHeaderSeparator ),
                     tableProps );
 
         propMgr.AddProperty( new PROPERTY<SCH_TABLE, int>( _HKI( "Border Width" ),
diff --git a/eeschema/sch_table.h b/eeschema/sch_table.h
index e1c163f252..48f593cd9a 100644
--- a/eeschema/sch_table.h
+++ b/eeschema/sch_table.h
@@ -53,8 +53,8 @@ public:
     void SetStrokeExternal( bool aDoStroke ) { m_strokeExternal = aDoStroke; }
     bool StrokeExternal() const              { return m_strokeExternal; }
 
-    void SetStrokeHeader( bool aDoStroke ) { m_strokeHeader = aDoStroke; }
-    bool StrokeHeader() const              { return m_strokeHeader; }
+    void SetStrokeHeaderSeparator( bool aDoStroke ) { m_StrokeHeaderSeparator = aDoStroke; }
+    bool StrokeHeaderSeparator() const              { return m_StrokeHeaderSeparator; }
 
     void SetBorderStroke( const STROKE_PARAMS& aParams ) { m_borderStroke = aParams; }
     const STROKE_PARAMS& GetBorderStroke() const { return m_borderStroke; }
@@ -238,7 +238,7 @@ public:
 
 protected:
     bool                        m_strokeExternal;
-    bool                        m_strokeHeader;
+    bool                        m_StrokeHeaderSeparator;
     STROKE_PARAMS               m_borderStroke;
     bool                        m_strokeRows;
     bool                        m_strokeColumns;
diff --git a/pcbnew/dialogs/dialog_table_properties.cpp b/pcbnew/dialogs/dialog_table_properties.cpp
index 370e2dac04..bf4368a0d6 100644
--- a/pcbnew/dialogs/dialog_table_properties.cpp
+++ b/pcbnew/dialogs/dialog_table_properties.cpp
@@ -211,7 +211,7 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataToWindow()
     m_cbLocked->SetValue( m_table->IsLocked() );
 
     m_borderCheckbox->SetValue( m_table->StrokeExternal() );
-    m_headerBorder->SetValue( m_table->StrokeHeader() );
+    m_headerBorder->SetValue( m_table->StrokeHeaderSeparator() );
 
     if( m_table->GetBorderStroke().GetWidth() >= 0 )
         m_borderWidth.SetValue( m_table->GetBorderStroke().GetWidth() );
@@ -223,9 +223,9 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataToWindow()
     else
         m_borderStyleCombo->SetSelection( 0 );
 
-    m_borderWidth.Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
-    m_borderStyleLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
-    m_borderStyleCombo->Enable( m_table->StrokeExternal() || m_table->StrokeHeader() );
+    m_borderWidth.Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
+    m_borderStyleLabel->Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
+    m_borderStyleCombo->Enable( m_table->StrokeExternal() || m_table->StrokeHeaderSeparator() );
 
     bool rows = m_table->StrokeRows() && m_table->GetSeparatorsStroke().GetWidth() >= 0;
     bool cols = m_table->StrokeColumns() && m_table->GetSeparatorsStroke().GetWidth() >= 0;
@@ -339,6 +339,10 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataFromWindow()
 
             wxString txt = m_grid->GetCellValue( row, col );
 
+            // Don't insert grey colour value back in to table cell
+            if( tableCell->GetColSpan() == 0 || tableCell->GetRowSpan() == 0 )
+                txt = wxEmptyString;
+
             // convert any text variable cross-references to their UUIDs
             txt = board->ConvertCrossReferencesToKIIDs( txt );
 
@@ -353,6 +357,7 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataFromWindow()
 #endif
 
             tableCell->SetText( txt );
+            tableCell->SetLayer( ToLAYER_ID( m_LayerSelectionCtrl->GetLayerSelection() ) );
         }
     }
 
@@ -360,7 +365,7 @@ bool DIALOG_TABLE_PROPERTIES::TransferDataFromWindow()
     m_table->SetLocked( m_cbLocked->GetValue() );
 
     m_table->SetStrokeExternal( m_borderCheckbox->GetValue() );
-    m_table->SetStrokeHeader( m_headerBorder->GetValue() );
+    m_table->SetStrokeHeaderSeparator( m_headerBorder->GetValue() );
     {
         STROKE_PARAMS stroke = m_table->GetBorderStroke();
 
diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp
index 8d5aad8ad2..d50415ff4d 100644
--- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp
+++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr.cpp
@@ -2129,9 +2129,9 @@ void PCB_IO_KICAD_SEXPR::format( const PCB_TABLE* aTable ) const
 
     m_out->Print( "(border" );
     KICAD_FORMAT::FormatBool( m_out, "external", aTable->StrokeExternal() );
-    KICAD_FORMAT::FormatBool( m_out, "header", aTable->StrokeHeader() );
+    KICAD_FORMAT::FormatBool( m_out, "header", aTable->StrokeHeaderSeparator() );
 
-    if( aTable->StrokeExternal() || aTable->StrokeHeader() )
+    if( aTable->StrokeExternal() || aTable->StrokeHeaderSeparator() )
         aTable->GetBorderStroke().Format( m_out, pcbIUScale );
 
     m_out->Print( ")" );                // Close `border` token.
diff --git a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp
index 1849d4be12..25fb1477f4 100644
--- a/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp
+++ b/pcbnew/pcb_io/kicad_sexpr/pcb_io_kicad_sexpr_parser.cpp
@@ -3932,7 +3932,7 @@ PCB_TABLE* PCB_IO_KICAD_SEXPR_PARSER::parsePCB_TABLE( BOARD_ITEM* aParent )
                     break;
 
                 case T_header:
-                    table->SetStrokeHeader( parseBool() );
+                    table->SetStrokeHeaderSeparator( parseBool() );
                     NeedRIGHT();
                     break;
 
diff --git a/pcbnew/pcb_painter.cpp b/pcbnew/pcb_painter.cpp
index 5c68c6f555..311daab5eb 100644
--- a/pcbnew/pcb_painter.cpp
+++ b/pcbnew/pcb_painter.cpp
@@ -2481,43 +2481,26 @@ void PCB_PAINTER::draw( const PCB_TABLE* aTable, int aLayer )
         return;
 
     for( PCB_TABLECELL* cell : aTable->GetCells() )
-        draw( static_cast<PCB_TEXTBOX*>( cell ), aLayer );
+    {
+        if( cell->GetColSpan() > 0 || cell->GetRowSpan() > 0 )
+            draw( static_cast<PCB_TEXTBOX*>( cell ), aLayer );
+    }
 
     // Selection for tables is done with a background wash, so pass in nullptr to GetColor()
     // so we just get the "normal" (un-selected/un-brightened) color for the borders.
-    COLOR4D    color = m_pcbSettings.GetColor( nullptr, aLayer );
-    int        lineWidth;
-    LINE_STYLE lineStyle;
+    COLOR4D color = m_pcbSettings.GetColor( nullptr, aLayer );
 
-    auto setupStroke =
-            [&]( const STROKE_PARAMS& stroke )
+    aTable->DrawBorders(
+            [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
             {
-                lineWidth = getLineThickness( stroke.GetWidth() );
-                lineStyle = stroke.GetLineStyle();
+                int        lineWidth = getLineThickness( stroke.GetWidth() );
+                LINE_STYLE lineStyle = stroke.GetLineStyle();
 
                 m_gal->SetIsFill( false );
                 m_gal->SetIsStroke( true );
                 m_gal->SetStrokeColor( color );
                 m_gal->SetLineWidth( lineWidth );
-            };
 
-    auto strokeShape =
-            [&]( const SHAPE& shape )
-            {
-                STROKE_PARAMS::Stroke( &shape, lineStyle, lineWidth, &m_pcbSettings,
-                        [&]( VECTOR2I a, VECTOR2I b )
-                        {
-                            // DrawLine has problem with 0 length lines so enforce minimum
-                            if( a == b )
-                                m_gal->DrawLine( a+1, b );
-                            else
-                                m_gal->DrawLine( a, b );
-                        } );
-            };
-
-    auto strokeLine =
-            [&]( VECTOR2I ptA, VECTOR2I ptB )
-            {
                 if( lineStyle <= LINE_STYLE::FIRST_TYPE )
                 {
                     m_gal->DrawLine( ptA, ptB );
@@ -2525,80 +2508,18 @@ void PCB_PAINTER::draw( const PCB_TABLE* aTable, int aLayer )
                 else
                 {
                     SHAPE_SEGMENT seg( ptA, ptB );
-                    strokeShape( seg );
+
+                    STROKE_PARAMS::Stroke( &seg, lineStyle, lineWidth, &m_pcbSettings,
+                            [&]( VECTOR2I a, VECTOR2I b )
+                            {
+                                // DrawLine has problem with 0 length lines so enforce minimum
+                                if( a == b )
+                                    m_gal->DrawLine( a+1, b );
+                                else
+                                    m_gal->DrawLine( a, b );
+                            } );
                 }
-            };
-
-    if( aTable->GetSeparatorsStroke().GetWidth() >= 0 )
-    {
-        setupStroke( aTable->GetSeparatorsStroke() );
-
-        // Stroke column edges
-        if( aTable->StrokeColumns() )
-        {
-            for( int col = 0; col < aTable->GetColCount() - 1; ++col )
-            {
-                int row = aTable->StrokeHeader() ? 0 : 1;
-
-                for( ; row < aTable->GetRowCount(); ++row )
-                {
-                    PCB_TABLECELL* cell = aTable->GetCell( row, col );
-                    std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
-
-                    if( corners.size() == 4 )
-                    {
-                        // Draw right edge (between adjacent cells)
-                        strokeLine( corners[1], corners[2] );
-                    }
-                }
-            }
-        }
-
-        // Stroke row edges
-        if( aTable->StrokeRows() )
-        {
-            for( int row = 0; row < aTable->GetRowCount() - 1; ++row )
-            {
-                for( int col = 0; col < aTable->GetColCount(); ++col )
-                {
-                    PCB_TABLECELL* cell = aTable->GetCell( row, col );
-                    std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
-
-                    if( corners.size() == 4 )
-                    {
-                        // Draw bottom edge (between adjacent cells)
-                        strokeLine( corners[2], corners[3] );
-                    }
-                }
-            }
-        }
-    }
-
-    if( aTable->GetBorderStroke().GetWidth() >= 0 )
-    {
-        setupStroke( aTable->GetBorderStroke() );
-
-        std::vector<VECTOR2I> topLeft     = aTable->GetCell( 0, 0 )->GetCornersInSequence();
-        std::vector<VECTOR2I> bottomLeft  = aTable->GetCell( aTable->GetRowCount() - 1, 0 )->GetCornersInSequence();
-        std::vector<VECTOR2I> topRight    = aTable->GetCell( 0, aTable->GetColCount() - 1 )->GetCornersInSequence();
-        std::vector<VECTOR2I> bottomRight = aTable->GetCell( aTable->GetRowCount() - 1, aTable->GetColCount() - 1 )->GetCornersInSequence();
-
-        if( aTable->StrokeHeader() )
-        {
-            strokeLine( topLeft[0], topRight[1] );
-            strokeLine( topLeft[0], topLeft[3] );
-            strokeLine( topLeft[3], topRight[2] );
-            strokeLine( topRight[1], topRight[2] );
-        }
-
-        if( aTable->StrokeExternal() )
-        {
-            strokeLine( topLeft[3], topRight[2] );
-            strokeLine( topRight[2], bottomRight[2] );
-            strokeLine( bottomRight[2], bottomLeft[3] );
-            strokeLine( bottomLeft[3], topLeft[3] );
-        }
-    }
+            } );
 
     // Highlight selected tablecells with a background wash.
     for( PCB_TABLECELL* cell : aTable->GetCells() )
diff --git a/pcbnew/pcb_table.cpp b/pcbnew/pcb_table.cpp
index 7eb187582f..4b789b4bd4 100644
--- a/pcbnew/pcb_table.cpp
+++ b/pcbnew/pcb_table.cpp
@@ -33,7 +33,7 @@
 PCB_TABLE::PCB_TABLE( BOARD_ITEM* aParent, int aLineWidth ) :
         BOARD_ITEM_CONTAINER( aParent, PCB_TABLE_T ),
         m_strokeExternal( true ),
-        m_strokeHeader( true ),
+        m_StrokeHeaderSeparator( true ),
         m_borderStroke( aLineWidth, LINE_STYLE::DEFAULT, COLOR4D::UNSPECIFIED ),
         m_strokeRows( true ),
         m_strokeColumns( true ),
@@ -47,7 +47,7 @@ PCB_TABLE::PCB_TABLE( const PCB_TABLE& aTable ) :
         BOARD_ITEM_CONTAINER( aTable )
 {
     m_strokeExternal = aTable.m_strokeExternal;
-    m_strokeHeader = aTable.m_strokeHeader;
+    m_StrokeHeaderSeparator = aTable.m_StrokeHeaderSeparator;
     m_borderStroke = aTable.m_borderStroke;
     m_strokeRows = aTable.m_strokeRows;
     m_strokeColumns = aTable.m_strokeColumns;
@@ -81,7 +81,7 @@ void PCB_TABLE::swapData( BOARD_ITEM* aImage )
     std::swap( m_isLocked, table->m_isLocked );
 
     std::swap( m_strokeExternal, table->m_strokeExternal );
-    std::swap( m_strokeHeader, table->m_strokeHeader );
+    std::swap( m_StrokeHeaderSeparator, table->m_StrokeHeaderSeparator );
     std::swap( m_borderStroke, table->m_borderStroke );
     std::swap( m_strokeRows, table->m_strokeRows );
     std::swap( m_strokeColumns, table->m_strokeColumns );
@@ -236,6 +236,70 @@ const BOX2I PCB_TABLE::GetBoundingBox() const
 }
 
 
+void PCB_TABLE::DrawBorders( const std::function<void( const VECTOR2I& aPt1,
+                                                       const VECTOR2I& aPt2,
+                                                       const STROKE_PARAMS& aStroke )>& aCallback ) const
+{
+    std::vector<VECTOR2I> topLeft     = GetCell( 0, 0 )->GetCornersInSequence();
+    std::vector<VECTOR2I> bottomLeft  = GetCell( GetRowCount() - 1, 0 )->GetCornersInSequence();
+    std::vector<VECTOR2I> topRight    = GetCell( 0, GetColCount() - 1 )->GetCornersInSequence();
+    std::vector<VECTOR2I> bottomRight = GetCell( GetRowCount() - 1, GetColCount() - 1 )->GetCornersInSequence();
+    STROKE_PARAMS         stroke;
+
+    for( int col = 0; col < GetColCount() - 1; ++col )
+    {
+        if( StrokeColumns() )
+            stroke = GetSeparatorsStroke();
+        else
+            continue;
+
+        for( int row = 0; row < GetRowCount(); ++row )
+        {
+            PCB_TABLECELL* cell = GetCell( row, col );
+
+            if( cell->GetColSpan() == 0 )
+                continue;
+
+            std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
+
+            if( corners.size() == 4 )
+                aCallback( corners[1], corners[2], stroke );
+        }
+    }
+
+    for( int row = 0; row < GetRowCount() - 1; ++row )
+    {
+        if( row == 0 && StrokeHeaderSeparator() )
+            stroke = GetBorderStroke();
+        else if( StrokeRows() )
+            stroke = GetSeparatorsStroke();
+        else
+            continue;
+
+        for( int col = 0; col < GetColCount(); ++col )
+        {
+            PCB_TABLECELL* cell = GetCell( row, col );
+
+            if( cell->GetRowSpan() == 0 )
+                continue;
+
+            std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
+
+            if( corners.size() == 4 )
+                aCallback( corners[2], corners[3], stroke );
+        }
+    }
+
+    if( StrokeExternal() && GetBorderStroke().GetWidth() >= 0 )
+    {
+        aCallback( topLeft[0], topRight[1], GetBorderStroke() );
+        aCallback( topRight[1], bottomRight[2], GetBorderStroke() );
+        aCallback( bottomRight[2], bottomLeft[3], GetBorderStroke() );
+        aCallback( bottomLeft[3], topLeft[0], GetBorderStroke() );
+    }
+}
+
+
 std::shared_ptr<SHAPE> PCB_TABLE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
 {
     std::vector<VECTOR2I> topLeft     = GetCell( 0, 0 )->GetCornersInSequence();
@@ -254,59 +318,11 @@ std::shared_ptr<SHAPE> PCB_TABLE::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHI
 
     shape->AddShape( new SHAPE_SIMPLE( pts ) );
 
-    auto addSeg =
-            [&shape]( const VECTOR2I& ptA, const VECTOR2I& ptB, int width )
+    DrawBorders(
+            [&shape]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
             {
-                shape->AddShape( new SHAPE_SEGMENT( ptA, ptB, width ) );
-            };
-
-    if( StrokeColumns() && GetSeparatorsStroke().GetWidth() >= 0)
-    {
-        for( int col = 0; col < GetColCount() - 1; ++col )
-        {
-            int row = StrokeHeader() ? 0 : 1;
-
-            for( ; row < GetRowCount(); ++row )
-            {
-                PCB_TABLECELL* cell = GetCell( row, col );
-                std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
-
-                if( corners.size() == 4 )
-                    addSeg( corners[1], corners[2], GetSeparatorsStroke().GetWidth() );
-            }
-        }
-    }
-
-    if( StrokeRows() && GetSeparatorsStroke().GetWidth() >= 0 )
-    {
-        for( int row = 0; row < GetRowCount() - 1; ++row )
-        {
-            for( int col = 0; col < GetColCount(); ++col )
-            {
-                PCB_TABLECELL* cell = GetCell( row, col );
-                std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
-
-                if( corners.size() == 4 )
-                    addSeg( corners[2], corners[3], GetSeparatorsStroke().GetWidth() );
-            }
-        }
-    }
-
-    if( StrokeHeader() && GetBorderStroke().GetWidth() >= 0 )
-    {
-        addSeg( topLeft[0], topRight[1], GetBorderStroke().GetWidth() );
-        addSeg( topLeft[0], topLeft[3], GetBorderStroke().GetWidth() );
-        addSeg( topLeft[3], topRight[2], GetBorderStroke().GetWidth() );
-        addSeg( topRight[1], topRight[2], GetBorderStroke().GetWidth() );
-    }
-
-    if( StrokeExternal() && GetBorderStroke().GetWidth() >= 0 )
-    {
-        addSeg( topLeft[3], topRight[2], GetBorderStroke().GetWidth() );
-        addSeg( topRight[2], bottomRight[2], GetBorderStroke().GetWidth() );
-        addSeg( bottomRight[2], bottomLeft[3], GetBorderStroke().GetWidth() );
-        addSeg( bottomLeft[3], topLeft[3], GetBorderStroke().GetWidth() );
-    }
+                shape->AddShape( new SHAPE_SEGMENT( ptA, ptB, stroke.GetWidth() ) );
+            } );
 
     return shape;
 }
@@ -321,7 +337,7 @@ void PCB_TABLE::TransformShapeToPolygon( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID a
     if( StrokeColumns() || StrokeRows() )
         gap = std::max( gap, aClearance + GetSeparatorsStroke().GetWidth() / 2 );
 
-    if( StrokeExternal() || StrokeHeader() )
+    if( StrokeExternal() || StrokeHeaderSeparator() )
         gap = std::max( gap, aClearance + GetBorderStroke().GetWidth() / 2 );
 
     for( PCB_TABLECELL* cell : m_cells )
@@ -456,7 +472,7 @@ bool PCB_TABLE::operator==( const PCB_TABLE& aOther ) const
     if( m_strokeExternal != aOther.m_strokeExternal )
         return false;
 
-    if( m_strokeHeader != aOther.m_strokeHeader )
+    if( m_StrokeHeaderSeparator != aOther.m_StrokeHeaderSeparator )
         return false;
 
     if( m_borderStroke != aOther.m_borderStroke )
@@ -502,7 +518,7 @@ double PCB_TABLE::Similarity( const BOARD_ITEM& aOther ) const
     if( m_strokeExternal != other.m_strokeExternal )
         similarity *= 0.9;
 
-    if( m_strokeHeader != other.m_strokeHeader )
+    if( m_StrokeHeaderSeparator != other.m_StrokeHeaderSeparator )
         similarity *= 0.9;
 
     if( m_borderStroke != other.m_borderStroke )
@@ -567,7 +583,7 @@ static struct PCB_TABLE_DESC
                     tableProps );
 
         propMgr.AddProperty( new PROPERTY<PCB_TABLE, bool>( _HKI( "Header Border" ),
-                    &PCB_TABLE::SetStrokeHeader, &PCB_TABLE::StrokeHeader ),
+                    &PCB_TABLE::SetStrokeHeaderSeparator, &PCB_TABLE::StrokeHeaderSeparator ),
                     tableProps );
 
         propMgr.AddProperty( new PROPERTY<PCB_TABLE, int>( _HKI( "Border Width" ),
diff --git a/pcbnew/pcb_table.h b/pcbnew/pcb_table.h
index ad90b1934d..e822bc5c2e 100644
--- a/pcbnew/pcb_table.h
+++ b/pcbnew/pcb_table.h
@@ -52,8 +52,8 @@ public:
     void SetStrokeExternal( bool aDoStroke ) { m_strokeExternal = aDoStroke; }
     bool StrokeExternal() const              { return m_strokeExternal; }
 
-    void SetStrokeHeader( bool aDoStroke ) { m_strokeHeader = aDoStroke; }
-    bool StrokeHeader() const              { return m_strokeHeader; }
+    void SetStrokeHeaderSeparator( bool aDoStroke ) { m_StrokeHeaderSeparator = aDoStroke; }
+    bool StrokeHeaderSeparator() const              { return m_StrokeHeaderSeparator; }
 
     void SetBorderStroke( const STROKE_PARAMS& aParams ) { m_borderStroke = aParams; }
     const STROKE_PARAMS& GetBorderStroke() const { return m_borderStroke; }
@@ -204,6 +204,10 @@ public:
 
     const BOX2I GetBoundingBox() const override;
 
+    void DrawBorders( const std::function<void( const VECTOR2I& aPt1,
+                                                const VECTOR2I& aPt2,
+                                                const STROKE_PARAMS& aStroke )>& aCallback ) const;
+
     // @copydoc BOARD_ITEM::GetEffectiveShape
     std::shared_ptr<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER,
                                               FLASHING aFlash = FLASHING::DEFAULT ) const override;
@@ -252,7 +256,7 @@ protected:
 
 protected:
     bool                        m_strokeExternal;
-    bool                        m_strokeHeader;
+    bool                        m_StrokeHeaderSeparator;
     STROKE_PARAMS               m_borderStroke;
     bool                        m_strokeRows;
     bool                        m_strokeColumns;
diff --git a/pcbnew/plot_brditems_plotter.cpp b/pcbnew/plot_brditems_plotter.cpp
index 95f09faa18..29ec281eb8 100644
--- a/pcbnew/plot_brditems_plotter.cpp
+++ b/pcbnew/plot_brditems_plotter.cpp
@@ -1122,8 +1122,6 @@ void BRDITEMS_PLOTTER::PlotTableBorders( const PCB_TABLE* aTable )
     if( !m_layerMask[aTable->GetLayer()] )
         return;
 
-    int          lineWidth;
-    LINE_STYLE   lineStyle;
     GBR_METADATA gbr_metadata;
 
     if( const FOOTPRINT* parentFP = aTable->GetParentFootprint() )
@@ -1132,27 +1130,12 @@ void BRDITEMS_PLOTTER::PlotTableBorders( const PCB_TABLE* aTable )
         gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
     }
 
-    auto setupStroke =
-            [&]( const STROKE_PARAMS& stroke )
+    aTable->DrawBorders(
+            [&]( const VECTOR2I& ptA, const VECTOR2I& ptB, const STROKE_PARAMS& stroke )
             {
-                lineWidth = stroke.GetWidth();
-                lineStyle = stroke.GetLineStyle();
-            };
+                int        lineWidth = stroke.GetWidth();
+                LINE_STYLE lineStyle = stroke.GetLineStyle();
 
-    auto strokeShape =
-            [&]( const SHAPE& shape )
-            {
-                STROKE_PARAMS::Stroke( &shape, lineStyle, lineWidth, m_plotter->RenderSettings(),
-                        [&]( const VECTOR2I& a, const VECTOR2I& b )
-                        {
-                            m_plotter->ThickSegment( a, b, lineWidth, GetPlotMode(),
-                                                     &gbr_metadata );
-                        } );
-            };
-
-    auto strokeLine =
-            [&]( const VECTOR2I& ptA, const VECTOR2I& ptB )
-            {
                 if( lineStyle <= LINE_STYLE::FIRST_TYPE )
                 {
                     m_plotter->ThickSegment( ptA, ptB, lineWidth, GetPlotMode(), &gbr_metadata );
@@ -1160,86 +1143,15 @@ void BRDITEMS_PLOTTER::PlotTableBorders( const PCB_TABLE* aTable )
                 else
                 {
                     SHAPE_SEGMENT seg( ptA, ptB );
-                    strokeShape( seg );
+
+                    STROKE_PARAMS::Stroke( &seg, lineStyle, lineWidth, m_plotter->RenderSettings(),
+                            [&]( const VECTOR2I& a, const VECTOR2I& b )
+                            {
+                                m_plotter->ThickSegment( a, b, lineWidth, GetPlotMode(),
+                                                         &gbr_metadata );
+                            } );
                 }
-            };
-
-    if( aTable->GetSeparatorsStroke().GetWidth() >= 0 )
-    {
-        setupStroke( aTable->GetSeparatorsStroke() );
-
-        if( aTable->StrokeColumns() )
-        {
-            for( int col = 0; col < aTable->GetColCount() - 1; ++col )
-            {
-                int row = 1;
-                if( aTable->StrokeHeader() )
-                {
-                    row = 0;
-                }
-
-                for( ; row < aTable->GetRowCount(); ++row )
-                {
-                    PCB_TABLECELL*        cell = aTable->GetCell( row, col );
-                    std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
-
-                    if( corners.size() == 4 )
-                    {
-                        // Draw right edge (between adjacent cells)
-                        strokeLine( corners[1], corners[2] );
-                    }
-                }
-            }
-        }
-
-        if( aTable->StrokeRows() )
-        {
-            for( int row = 0; row < aTable->GetRowCount() - 1; ++row )
-            {
-                for( int col = 0; col < aTable->GetColCount(); ++col )
-                {
-                    PCB_TABLECELL*        cell = aTable->GetCell( row, col );
-                    std::vector<VECTOR2I> corners = cell->GetCornersInSequence();
-
-                    if( corners.size() == 4 )
-                    {
-                        // Draw bottom edge (between adjacent cells)
-                        strokeLine( corners[2], corners[3] );
-                    }
-                }
-            }
-        }
-    }
-
-    if( aTable->GetBorderStroke().GetWidth() >= 0 )
-    {
-        setupStroke( aTable->GetBorderStroke() );
-
-        std::vector<VECTOR2I> topLeft = aTable->GetCell( 0, 0 )->GetCornersInSequence();
-        std::vector<VECTOR2I> bottomLeft =
-                aTable->GetCell( aTable->GetRowCount() - 1, 0 )->GetCornersInSequence();
-        std::vector<VECTOR2I> topRight =
-                aTable->GetCell( 0, aTable->GetColCount() - 1 )->GetCornersInSequence();
-        std::vector<VECTOR2I> bottomRight =
-                aTable->GetCell( aTable->GetRowCount() - 1, aTable->GetColCount() - 1 )
-                        ->GetCornersInSequence();
-
-        if( aTable->StrokeHeader() )
-        {
-            strokeLine( topLeft[0], topRight[1] );
-            strokeLine( topLeft[0], topLeft[3] );
-            strokeLine( topLeft[3], topRight[2] );
-            strokeLine( topRight[1], topRight[2] );
-        }
-
-        if( aTable->StrokeExternal() )
-        {
-            strokeLine( topLeft[3], topRight[2] );
-            strokeLine( topRight[2], bottomRight[2] );
-            strokeLine( bottomRight[2], bottomLeft[3] );
-            strokeLine( bottomLeft[3], topLeft[3] );
-        }
-    }
+            } );
 }