mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2024-11-24 00:34:47 +00:00
a724b3d8b1
First, ensure that we calculate the bbox of the imported elements. If the total BBOX is outside our allowed value, refused to import. This needs to be scaled or addressed in an external editor If the bbox fits, then make sure that we clamp it such that the largest element is still within our bounds Fixes https://gitlab.com/kicad/code/kicad/-/issues/18523
436 lines
15 KiB
C++
436 lines
15 KiB
C++
/*
|
|
* This program source code file is part of KICAD, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2016 CERN
|
|
* Copyright (C) 1992-2023 KiCad Developers, see AUTHORS.txt for contributors.
|
|
* @author Janito V. Ferreira Filho
|
|
*
|
|
* 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 "svg_import_plugin.h"
|
|
|
|
#include <nanosvg.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <locale_io.h>
|
|
|
|
#include <eda_item.h>
|
|
#include "graphics_importer.h"
|
|
|
|
static const int SVG_DPI = 96;
|
|
|
|
static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
|
|
std::function< const float&( const float&, const float& ) > comparator );
|
|
static float calculateBezierSegmentationThreshold( const float* aCurvePoints );
|
|
static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
|
|
float aStep, const float* aCurvePoints, float aSegmentationThreshold,
|
|
std::vector< VECTOR2D >& aGeneratedPoints );
|
|
static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
|
|
const VECTOR2D& aEnd, float aOffset, float aStep, const float* aCurvePoints,
|
|
float aSegmentationThreshold, std::vector< VECTOR2D >& aGeneratedPoints );
|
|
static VECTOR2D getBezierPoint( const float* aCurvePoints, float aStep );
|
|
static VECTOR2D getPoint( const float* aPointCoordinates );
|
|
static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
|
|
float aDistance );
|
|
static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
|
|
const VECTOR2D& aLineEnd );
|
|
|
|
|
|
bool SVG_IMPORT_PLUGIN::Load( const wxString& aFileName )
|
|
{
|
|
wxCHECK( m_importer, false );
|
|
|
|
LOCALE_IO toggle; // switch on/off the locale "C" notation
|
|
|
|
// 1- wxFopen takes care of unicode filenames across platforms
|
|
// 2 - nanosvg (exactly nsvgParseFromFile) expects a binary file (exactly the CRLF eof must
|
|
// not be replaced by LF and changes the byte count) in one validity test,
|
|
// so open it in binary mode.
|
|
FILE* fp = wxFopen( aFileName, wxT( "rb" ) );
|
|
|
|
if( fp == nullptr )
|
|
return false;
|
|
|
|
// nsvgParseFromFile will close the file after reading
|
|
m_parsedImage = nsvgParseFromFile( fp, "mm", SVG_DPI );
|
|
|
|
wxCHECK( m_parsedImage, false );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SVG_IMPORT_PLUGIN::LoadFromMemory( const wxMemoryBuffer& aMemBuffer )
|
|
{
|
|
wxCHECK( m_importer, false );
|
|
|
|
LOCALE_IO toggle; // switch on/off the locale "C" notation
|
|
|
|
std::string str( reinterpret_cast<char*>( aMemBuffer.GetData() ), aMemBuffer.GetDataLen() );
|
|
wxCHECK( str.data()[aMemBuffer.GetDataLen()] == '\0', false );
|
|
|
|
// nsvgParse will modify the string data
|
|
m_parsedImage = nsvgParse( str.data(), "mm", SVG_DPI );
|
|
|
|
wxCHECK( m_parsedImage, false );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool SVG_IMPORT_PLUGIN::Import()
|
|
{
|
|
auto alpha =
|
|
[]( unsigned int color )
|
|
{
|
|
return color >> 24;
|
|
};
|
|
|
|
for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
|
|
{
|
|
if( !( shape->flags & NSVG_FLAGS_VISIBLE ) )
|
|
continue;
|
|
|
|
if( shape->stroke.type == NSVG_PAINT_NONE && shape->fill.type == NSVG_PAINT_NONE )
|
|
continue;
|
|
|
|
double lineWidth = shape->stroke.type != NSVG_PAINT_NONE ? shape->strokeWidth : -1;
|
|
bool filled = shape->fill.type != NSVG_PAINT_NONE && alpha( shape->fill.color ) > 0;
|
|
|
|
COLOR4D fillColor = COLOR4D::UNSPECIFIED;
|
|
|
|
if( shape->fill.type == NSVG_PAINT_COLOR )
|
|
{
|
|
unsigned int icolor = shape->fill.color;
|
|
|
|
fillColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
fillColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
fillColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
fillColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
|
|
// nanosvg probably didn't read it properly, use default
|
|
if( fillColor == COLOR4D::BLACK )
|
|
fillColor = COLOR4D::UNSPECIFIED;
|
|
}
|
|
|
|
COLOR4D strokeColor = COLOR4D::UNSPECIFIED;
|
|
|
|
if( shape->stroke.type == NSVG_PAINT_COLOR )
|
|
{
|
|
unsigned int icolor = shape->stroke.color;
|
|
|
|
strokeColor.r = std::clamp( ( icolor >> 0 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
strokeColor.g = std::clamp( ( icolor >> 8 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
strokeColor.b = std::clamp( ( icolor >> 16 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
strokeColor.a = std::clamp( ( icolor >> 24 ) & 0xFF, 0u, 255u ) / 255.0;
|
|
|
|
// nanosvg probably didn't read it properly, use default
|
|
if( strokeColor == COLOR4D::BLACK )
|
|
strokeColor = COLOR4D::UNSPECIFIED;
|
|
}
|
|
|
|
LINE_STYLE dashType = LINE_STYLE::SOLID;
|
|
|
|
if( shape->strokeDashCount > 0 )
|
|
{
|
|
float* dashArray = shape->strokeDashArray;
|
|
|
|
int dotCount = 0;
|
|
int dashCount = 0;
|
|
|
|
const float dashThreshold = shape->strokeWidth * 1.9f;
|
|
|
|
for( int i = 0; i < shape->strokeDashCount; i += 2 )
|
|
{
|
|
if( dashArray[i] < dashThreshold )
|
|
dotCount++;
|
|
else
|
|
dashCount++;
|
|
}
|
|
|
|
if( dotCount > 0 && dashCount == 0 )
|
|
dashType = LINE_STYLE::DOT;
|
|
else if( dotCount == 0 && dashCount > 0 )
|
|
dashType = LINE_STYLE::DASH;
|
|
else if( dotCount == 1 && dashCount == 1 )
|
|
dashType = LINE_STYLE::DASHDOT;
|
|
else if( dotCount == 2 && dashCount == 1 )
|
|
dashType = LINE_STYLE::DASHDOTDOT;
|
|
}
|
|
|
|
IMPORTED_STROKE stroke( lineWidth, dashType, strokeColor );
|
|
|
|
GRAPHICS_IMPORTER::POLY_FILL_RULE rule = GRAPHICS_IMPORTER::PF_NONZERO;
|
|
|
|
switch( shape->fillRule )
|
|
{
|
|
case NSVG_FILLRULE_NONZERO: rule = GRAPHICS_IMPORTER::PF_NONZERO; break;
|
|
case NSVG_FILLRULE_EVENODD: rule = GRAPHICS_IMPORTER::PF_EVEN_ODD; break;
|
|
default: break;
|
|
}
|
|
|
|
m_internalImporter.NewShape( rule );
|
|
|
|
for( NSVGpath* path = shape->paths; path != nullptr; path = path->next )
|
|
{
|
|
bool closed = path->closed || filled || rule == GRAPHICS_IMPORTER::PF_EVEN_ODD;
|
|
|
|
DrawPath( path->pts, path->npts, closed, stroke, filled, fillColor );
|
|
}
|
|
}
|
|
|
|
m_internalImporter.PostprocessNestedPolygons();
|
|
wxCHECK( m_importer, false );
|
|
m_internalImporter.ImportTo( *m_importer );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
double SVG_IMPORT_PLUGIN::GetImageHeight() const
|
|
{
|
|
if( !m_parsedImage )
|
|
{
|
|
wxASSERT_MSG( false, wxT( "Image must have been loaded before checking height" ) );
|
|
return 0.0;
|
|
}
|
|
|
|
return m_parsedImage->height / SVG_DPI * inches2mm;
|
|
}
|
|
|
|
|
|
double SVG_IMPORT_PLUGIN::GetImageWidth() const
|
|
{
|
|
if( !m_parsedImage )
|
|
{
|
|
wxASSERT_MSG( false, wxT( "Image must have been loaded before checking width" ) );
|
|
return 0.0;
|
|
}
|
|
|
|
return m_parsedImage->width / SVG_DPI * inches2mm;
|
|
}
|
|
|
|
|
|
BOX2D SVG_IMPORT_PLUGIN::GetImageBBox() const
|
|
{
|
|
BOX2D bbox;
|
|
|
|
if( !m_parsedImage || !m_parsedImage->shapes )
|
|
{
|
|
wxASSERT_MSG( false, wxT( "Image must have been loaded before getting bbox" ) );
|
|
return bbox;
|
|
}
|
|
|
|
for( NSVGshape* shape = m_parsedImage->shapes; shape != nullptr; shape = shape->next )
|
|
{
|
|
BOX2D shapeBbox;
|
|
float( &bounds )[4] = shape->bounds;
|
|
|
|
shapeBbox.SetOrigin( bounds[0], bounds[1] );
|
|
shapeBbox.SetEnd( bounds[2], bounds[3] );
|
|
|
|
bbox.Merge( shapeBbox );
|
|
}
|
|
|
|
return bbox;
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawPath( const float* aPoints, int aNumPoints, bool aClosedPath,
|
|
const IMPORTED_STROKE& aStroke, bool aFilled,
|
|
const COLOR4D& aFillColor )
|
|
{
|
|
std::vector<VECTOR2D> collectedPathPoints;
|
|
|
|
if( aNumPoints > 0 )
|
|
DrawCubicBezierPath( aPoints, aNumPoints, collectedPathPoints );
|
|
|
|
if( aClosedPath && collectedPathPoints.size() > 2 )
|
|
DrawPolygon( collectedPathPoints, aStroke, aFilled, aFillColor );
|
|
else
|
|
DrawLineSegments( collectedPathPoints, aStroke );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawCubicBezierPath( const float* aPoints, int aNumPoints,
|
|
std::vector<VECTOR2D>& aGeneratedPoints )
|
|
{
|
|
const int pointsPerSegment = 4;
|
|
const int curveSpecificPointsPerSegment = 3;
|
|
const int curveSpecificCoordinatesPerSegment = 2 * curveSpecificPointsPerSegment;
|
|
const float* currentPoints = aPoints;
|
|
int remainingPoints = aNumPoints;
|
|
|
|
while( remainingPoints >= pointsPerSegment )
|
|
{
|
|
DrawCubicBezierCurve( currentPoints, aGeneratedPoints );
|
|
currentPoints += curveSpecificCoordinatesPerSegment;
|
|
remainingPoints -= curveSpecificPointsPerSegment;
|
|
}
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawCubicBezierCurve( const float* aPoints,
|
|
std::vector<VECTOR2D>& aGeneratedPoints )
|
|
{
|
|
auto start = getBezierPoint( aPoints, 0.0f );
|
|
auto end = getBezierPoint( aPoints, 1.0f );
|
|
auto segmentationThreshold = calculateBezierSegmentationThreshold( aPoints );
|
|
|
|
aGeneratedPoints.push_back( start );
|
|
segmentBezierCurve( start, end, 0.0f, 0.5f, aPoints, segmentationThreshold, aGeneratedPoints );
|
|
aGeneratedPoints.push_back( end );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawPolygon( const std::vector<VECTOR2D>& aPoints,
|
|
const IMPORTED_STROKE& aStroke, bool aFilled,
|
|
const COLOR4D& aFillColor )
|
|
{
|
|
m_internalImporter.AddPolygon( aPoints, aStroke, aFilled, aFillColor );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::DrawLineSegments( const std::vector<VECTOR2D>& aPoints,
|
|
const IMPORTED_STROKE& aStroke )
|
|
{
|
|
unsigned int numLineStartPoints = aPoints.size() - 1;
|
|
|
|
for( unsigned int pointIndex = 0; pointIndex < numLineStartPoints; ++pointIndex )
|
|
m_internalImporter.AddLine( aPoints[pointIndex], aPoints[pointIndex + 1], aStroke );
|
|
}
|
|
|
|
|
|
static VECTOR2D getPoint( const float* aPointCoordinates )
|
|
{
|
|
return VECTOR2D( aPointCoordinates[0], aPointCoordinates[1] );
|
|
}
|
|
|
|
|
|
static VECTOR2D getBezierPoint( const float* aPoints, float aStep )
|
|
{
|
|
const int coordinatesPerPoint = 2;
|
|
|
|
auto firstCubicPoint = getPoint( aPoints );
|
|
auto secondCubicPoint = getPoint( aPoints + 1 * coordinatesPerPoint );
|
|
auto thirdCubicPoint = getPoint( aPoints + 2 * coordinatesPerPoint );
|
|
auto fourthCubicPoint = getPoint( aPoints + 3 * coordinatesPerPoint );
|
|
|
|
auto firstQuadraticPoint = getPointInLine( firstCubicPoint, secondCubicPoint, aStep );
|
|
auto secondQuadraticPoint = getPointInLine( secondCubicPoint, thirdCubicPoint, aStep );
|
|
auto thirdQuadraticPoint = getPointInLine( thirdCubicPoint, fourthCubicPoint, aStep );
|
|
|
|
auto firstLinearPoint = getPointInLine( firstQuadraticPoint, secondQuadraticPoint, aStep );
|
|
auto secondLinearPoint = getPointInLine( secondQuadraticPoint, thirdQuadraticPoint, aStep );
|
|
|
|
return getPointInLine( firstLinearPoint, secondLinearPoint, aStep );
|
|
}
|
|
|
|
|
|
static VECTOR2D getPointInLine( const VECTOR2D& aLineStart, const VECTOR2D& aLineEnd,
|
|
float aDistance )
|
|
{
|
|
return aLineStart + ( aLineEnd - aLineStart ) * aDistance;
|
|
}
|
|
|
|
|
|
static float calculateBezierSegmentationThreshold( const float* aCurvePoints )
|
|
{
|
|
using comparatorFunction = const float&(*)( const float&, const float& );
|
|
|
|
auto minimumComparator = static_cast< comparatorFunction >( &std::min );
|
|
auto maximumComparator = static_cast< comparatorFunction >( &std::max );
|
|
|
|
VECTOR2D minimum = calculateBezierBoundingBoxExtremity( aCurvePoints, minimumComparator );
|
|
VECTOR2D maximum = calculateBezierBoundingBoxExtremity( aCurvePoints, maximumComparator );
|
|
VECTOR2D boundingBoxDimensions = maximum - minimum;
|
|
|
|
return 0.001 * std::max( boundingBoxDimensions.x, boundingBoxDimensions.y );
|
|
}
|
|
|
|
|
|
static VECTOR2D calculateBezierBoundingBoxExtremity( const float* aCurvePoints,
|
|
std::function< const float&( const float&, const float& ) > comparator )
|
|
{
|
|
float x = aCurvePoints[0];
|
|
float y = aCurvePoints[1];
|
|
|
|
for( int pointIndex = 1; pointIndex < 3; ++pointIndex )
|
|
{
|
|
x = comparator( x, aCurvePoints[ 2 * pointIndex ] );
|
|
y = comparator( y, aCurvePoints[ 2 * pointIndex + 1 ] );
|
|
}
|
|
|
|
return VECTOR2D( x, y );
|
|
}
|
|
|
|
|
|
static void segmentBezierCurve( const VECTOR2D& aStart, const VECTOR2D& aEnd, float aOffset,
|
|
float aStep, const float* aCurvePoints,
|
|
float aSegmentationThreshold,
|
|
std::vector< VECTOR2D >& aGeneratedPoints )
|
|
{
|
|
VECTOR2D middle = getBezierPoint( aCurvePoints, aOffset + aStep );
|
|
float distanceToPreviousSegment = distanceFromPointToLine( middle, aStart, aEnd );
|
|
|
|
if( distanceToPreviousSegment > aSegmentationThreshold )
|
|
{
|
|
createNewBezierCurveSegments( aStart, middle, aEnd, aOffset, aStep, aCurvePoints,
|
|
aSegmentationThreshold, aGeneratedPoints );
|
|
}
|
|
}
|
|
|
|
|
|
static void createNewBezierCurveSegments( const VECTOR2D& aStart, const VECTOR2D& aMiddle,
|
|
const VECTOR2D& aEnd, float aOffset, float aStep,
|
|
const float* aCurvePoints, float aSegmentationThreshold,
|
|
std::vector< VECTOR2D >& aGeneratedPoints )
|
|
{
|
|
float newStep = aStep / 2.f;
|
|
float offsetAfterMiddle = aOffset + aStep;
|
|
|
|
segmentBezierCurve( aStart, aMiddle, aOffset, newStep, aCurvePoints, aSegmentationThreshold,
|
|
aGeneratedPoints );
|
|
|
|
aGeneratedPoints.push_back( aMiddle );
|
|
|
|
segmentBezierCurve( aMiddle, aEnd, offsetAfterMiddle, newStep, aCurvePoints,
|
|
aSegmentationThreshold, aGeneratedPoints );
|
|
}
|
|
|
|
|
|
static float distanceFromPointToLine( const VECTOR2D& aPoint, const VECTOR2D& aLineStart,
|
|
const VECTOR2D& aLineEnd )
|
|
{
|
|
auto lineDirection = aLineEnd - aLineStart;
|
|
auto lineNormal = lineDirection.Perpendicular().Resize( 1.f );
|
|
auto lineStartToPoint = aPoint - aLineStart;
|
|
|
|
auto distance = lineNormal.Dot( lineStartToPoint );
|
|
|
|
return fabs( distance );
|
|
}
|
|
|
|
|
|
void SVG_IMPORT_PLUGIN::ReportMsg( const wxString& aMessage )
|
|
{
|
|
// Add message to keep trace of not handled svg entities
|
|
m_messages += aMessage;
|
|
m_messages += '\n';
|
|
} |