diff --git a/common/import_gfx/graphics_importer_buffer.cpp b/common/import_gfx/graphics_importer_buffer.cpp index a824f0aa04..4f0fc24ab5 100644 --- a/common/import_gfx/graphics_importer_buffer.cpp +++ b/common/import_gfx/graphics_importer_buffer.cpp @@ -131,10 +131,12 @@ void GRAPHICS_IMPORTER_BUFFER::ImportTo( GRAPHICS_IMPORTER& aImporter ) // the graphics in the KiCad drawing area. else if( aImporter.GetImportOffsetMM() == VECTOR2D( 0, 0 ) ) { - if( boundingBox.GetRight() > std::numeric_limits::max() - || boundingBox.GetBottom() > std::numeric_limits::max() - || boundingBox.GetLeft() < std::numeric_limits::min() - || boundingBox.GetTop() < std::numeric_limits::min() ) + double iuFactor = aImporter.GetMillimeterToIuFactor(); + + if( boundingBox.GetRight() * iuFactor > std::numeric_limits::max() + || boundingBox.GetBottom() * iuFactor > std::numeric_limits::max() + || boundingBox.GetLeft() * iuFactor < std::numeric_limits::min() + || boundingBox.GetTop() * iuFactor < std::numeric_limits::min() ) { VECTOR2D offset = boundingBox.GetOrigin(); aImporter.SetImportOffsetMM( -offset ); diff --git a/qa/data/pcbnew/large_coords_test.dxf b/qa/data/pcbnew/large_coords_test.dxf new file mode 100644 index 0000000000..d9eba13f11 --- /dev/null +++ b/qa/data/pcbnew/large_coords_test.dxf @@ -0,0 +1,70 @@ +0 +SECTION +2 +HEADER +9 +$ACADVER +1 +AC1018 +9 +$INSUNITS +70 +4 +9 +$EXTMIN +10 +2300.0 +20 +1400.0 +30 +0.0 +9 +$EXTMAX +10 +2350.0 +20 +1450.0 +30 +0.0 +0 +ENDSEC +0 +SECTION +2 +ENTITIES +0 +LINE +8 +0 +10 +2300.0 +20 +1400.0 +30 +0.0 +11 +2350.0 +21 +1450.0 +31 +0.0 +0 +ARC +8 +0 +10 +2325.0 +20 +1425.0 +30 +0.0 +40 +10.0 +50 +0.0 +51 +90.0 +0 +ENDSEC +0 +EOF diff --git a/qa/tests/common/CMakeLists.txt b/qa/tests/common/CMakeLists.txt index df70d29e6d..dca8bc00b1 100644 --- a/qa/tests/common/CMakeLists.txt +++ b/qa/tests/common/CMakeLists.txt @@ -92,6 +92,9 @@ set( QA_COMMON_SRCS # GAL tests test_gal_xor_mode.cpp + # Graphics import tests + test_graphics_importer_buffer.cpp + # Project template tests (source included directly since it's from kicad executable) test_project_template.cpp ${CMAKE_SOURCE_DIR}/kicad/project_template.cpp diff --git a/qa/tests/common/test_graphics_importer_buffer.cpp b/qa/tests/common/test_graphics_importer_buffer.cpp new file mode 100644 index 0000000000..a611a26bd5 --- /dev/null +++ b/qa/tests/common/test_graphics_importer_buffer.cpp @@ -0,0 +1,145 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.TXT for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include + +#include +#include +#include +#include + + +class TEST_GRAPHICS_IMPORTER : public GRAPHICS_IMPORTER +{ +public: + TEST_GRAPHICS_IMPORTER() : GRAPHICS_IMPORTER() + { + m_millimeterToIu = PCB_IU_PER_MM; + } + + void AddLine( const VECTOR2D& aStart, const VECTOR2D& aEnd, + const IMPORTED_STROKE& aStroke ) override + { + m_lines.push_back( { aStart, aEnd } ); + } + + void AddCircle( const VECTOR2D& aCenter, double aRadius, const IMPORTED_STROKE& aStroke, + bool aFilled, const COLOR4D& aFillColor ) override {} + + void AddArc( const VECTOR2D& aCenter, const VECTOR2D& aStart, const EDA_ANGLE& aAngle, + const IMPORTED_STROKE& aStroke ) override {} + + void AddPolygon( const std::vector& aVertices, const IMPORTED_STROKE& aStroke, + bool aFilled, const COLOR4D& aFillColor ) override {} + + void AddText( const VECTOR2D& aOrigin, const wxString& aText, double aHeight, double aWidth, + double aThickness, double aOrientation, GR_TEXT_H_ALIGN_T aHJustify, + GR_TEXT_V_ALIGN_T aVJustify, const COLOR4D& aColor ) override {} + + void AddSpline( const VECTOR2D& aStart, const VECTOR2D& aBezierControl1, + const VECTOR2D& aBezierControl2, const VECTOR2D& aEnd, + const IMPORTED_STROKE& aStroke ) override {} + + std::vector> m_lines; +}; + + +BOOST_AUTO_TEST_SUITE( GraphicsImporterBuffer ) + + +/** + * Test that large coordinates that would overflow when converted to internal units + * are automatically offset to fit within the valid coordinate range. + * + * This tests the fix for GitLab issue #9681 where DXF files with large coordinates + * (e.g., around 2300mm which exceeds INT_MAX/1e6 ~= 2147mm) caused visual corruption. + */ +BOOST_AUTO_TEST_CASE( LargeCoordinatesAutoOffset ) +{ + GRAPHICS_IMPORTER_BUFFER buffer; + TEST_GRAPHICS_IMPORTER importer; + + // Add a line with coordinates that would overflow INT32 when converted to IU + // 2300mm * 1e6 IU/mm = 2.3e9 > INT32_MAX (2.147e9) + VECTOR2D start( 2300.0, 1400.0 ); + VECTOR2D end( 2350.0, 1450.0 ); + + IMPORTED_STROKE stroke( 0.1 ); + buffer.AddLine( start, end, stroke ); + + // Import with no offset set (default is 0,0) + BOOST_CHECK( importer.GetImportOffsetMM() == VECTOR2D( 0, 0 ) ); + + buffer.ImportTo( importer ); + + // After import, the offset should have been automatically adjusted + // to move the coordinates into the valid range + VECTOR2D offset = importer.GetImportOffsetMM(); + + // The offset should be negative of the bounding box origin to bring coordinates near zero + BOOST_CHECK_MESSAGE( offset.x < 0, "X offset should be negative to shift large coords down" ); + + // Verify the resulting coordinates are within valid IU range + if( !importer.m_lines.empty() ) + { + VECTOR2D importedStart = importer.m_lines[0].first; + VECTOR2D importedEnd = importer.m_lines[0].second; + + // After offset and scale, coordinates should be manageable + double maxCoord = static_cast( std::numeric_limits::max() ) + / importer.GetMillimeterToIuFactor(); + + BOOST_CHECK_MESSAGE( ( importedStart.x + offset.x ) < maxCoord, + "Imported X coord should be within valid range" ); + BOOST_CHECK_MESSAGE( ( importedStart.y + offset.y ) < maxCoord, + "Imported Y coord should be within valid range" ); + } +} + + +/** + * Test that normal coordinates (within valid range) are not modified. + */ +BOOST_AUTO_TEST_CASE( NormalCoordinatesNoOffset ) +{ + GRAPHICS_IMPORTER_BUFFER buffer; + TEST_GRAPHICS_IMPORTER importer; + + // Add a line with normal coordinates that fit within the valid range + // 100mm * 1e6 IU/mm = 1e8 << INT32_MAX + VECTOR2D start( 100.0, 100.0 ); + VECTOR2D end( 200.0, 200.0 ); + + IMPORTED_STROKE stroke( 0.1 ); + buffer.AddLine( start, end, stroke ); + + buffer.ImportTo( importer ); + + // Offset should remain at zero since coordinates are valid + VECTOR2D offset = importer.GetImportOffsetMM(); + BOOST_CHECK_MESSAGE( offset == VECTOR2D( 0, 0 ), + "Normal coordinates should not trigger auto-offset" ); +} + + +BOOST_AUTO_TEST_SUITE_END()