Add a cache for TrueType contours and triangulation data.

Also returns minimumSegmentLength to its former value as
it doesn't appear to be required to fix
https://gitlab.com/kicad/code/kicad/-/issues/11463.

Fixes: https://gitlab.com/kicad/code/kicad/-/issues/16568
This commit is contained in:
Jeff Young 2024-01-12 17:04:05 +00:00
parent fc66cb6805
commit 162e8962f7
9 changed files with 193 additions and 91 deletions

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski <gitlab@rinta-koski.net>
* Copyright (C) 2021-2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 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
@ -122,10 +122,7 @@ void OUTLINE_GLYPH::Triangulate( std::function<void( const VECTOR2I& aPt1,
const VECTOR2I& aPt2,
const VECTOR2I& aPt3 )> aCallback ) const
{
// Only call CacheTriangulation if it has never been done before. Otherwise we'll hash
// the triangulation to see if it has been edited, and glyphs after creation are read-only.
if( TriangulatedPolyCount() == 0 )
const_cast<OUTLINE_GLYPH*>( this )->CacheTriangulation( false );
const_cast<OUTLINE_GLYPH*>( this )->CacheTriangulation( false );
for( unsigned int i = 0; i < TriangulatedPolyCount(); i++ )
{
@ -141,3 +138,30 @@ void OUTLINE_GLYPH::Triangulate( std::function<void( const VECTOR2I& aPt1,
}
void OUTLINE_GLYPH::CacheTriangulation( bool aPartition, bool aSimplify )
{
// Only call CacheTriangulation if it has never been done before. Otherwise we'll hash
// the triangulation to see if it has been edited, and glyphs are invariant after creation.
//
// Also forces "partition" to false as we never want to partition a glyph.
if( TriangulatedPolyCount() == 0 )
SHAPE_POLY_SET::CacheTriangulation( false, aSimplify );
}
std::vector<std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>> OUTLINE_GLYPH::GetTriangulationData() const
{
std::vector<std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>> data;
for( const std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>& poly : m_triangulatedPolys )
data.push_back( std::make_unique<SHAPE_POLY_SET::TRIANGULATED_POLYGON>( *poly ) );
return data;
}
void OUTLINE_GLYPH::CacheTriangulation( std::vector<std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>>& aHintData )
{
cacheTriangulation( false, false, &aHintData );
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski <gitlab@rinta-koski.net>
* Copyright (C) 2021 Kicad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* Outline font class
*
@ -39,8 +39,8 @@ OUTLINE_DECOMPOSER::OUTLINE_DECOMPOSER( FT_Outline& aOutline ) :
static VECTOR2D toVector2D( const FT_Vector* aFreeTypeVector )
{
return VECTOR2D( aFreeTypeVector->x * GLYPH_SIZE_SCALER,
aFreeTypeVector->y * GLYPH_SIZE_SCALER );
return VECTOR2D( (double) aFreeTypeVector->x * GLYPH_SIZE_SCALER,
(double) aFreeTypeVector->y * GLYPH_SIZE_SCALER );
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski <gitlab@rinta-koski.net>
* Copyright (C) 2021-2023 Kicad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 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
@ -255,6 +255,38 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphs( BOX2I* aBBox, std::vector<std::unique_pt
aOrigin, aTextStyle );
}
struct GLYPH_CACHE_KEY {
FT_Face face;
hb_codepoint_t codepoint;
bool fakeItalic;
bool fakeBold;
bool mirror;
EDA_ANGLE angle;
bool operator==(const GLYPH_CACHE_KEY& rhs ) const
{
return face == rhs.face && codepoint == rhs.codepoint
&& fakeItalic == rhs.fakeItalic && fakeBold == rhs.fakeBold
&& mirror == rhs.mirror && angle == rhs.angle;
}
};
namespace std
{
template <>
struct hash<GLYPH_CACHE_KEY>
{
std::size_t operator()( const GLYPH_CACHE_KEY& k ) const
{
return hash<const void*>()( k.face ) ^ hash<unsigned>()( k.codepoint )
^ hash<int>()( k.fakeItalic ) ^ hash<int>()( k.fakeBold )
^ hash<int>()( k.mirror ) ^ hash<int>()( k.angle.AsTenthsOfADegree() );
}
};
}
VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
std::vector<std::unique_ptr<GLYPH>>* aGlyphs,
const wxString& aText, const VECTOR2I& aSize,
@ -295,6 +327,10 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
if( aGlyphs )
aGlyphs->reserve( glyphCount );
// GLYPH_DATA is a collection of all outlines in the glyph; for example the 'o' glyph
// generally contains 2 contours, one for the glyph outline and one for the hole
static std::unordered_map<GLYPH_CACHE_KEY, GLYPH_DATA> s_glyphCache;
for( unsigned int i = 0; i < glyphCount; i++ )
{
// Don't process glyphs that were already included in a previous cluster
@ -303,64 +339,67 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
if( aGlyphs )
{
if( m_fakeItal )
GLYPH_CACHE_KEY key = { face, glyphInfo[i].codepoint, m_fakeItal, m_fakeBold,
aMirror, aAngle };
GLYPH_DATA& glyphData = s_glyphCache[ key ];
if( glyphData.m_Contours.empty() )
{
FT_Matrix matrix;
// Create a 12 degree slant
const float angle = (float)( -M_PI * 12.0f ) / 180.0f;
matrix.xx = (FT_Fixed) ( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed) ( -sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed) ( 0 * 0x10000L ); // Don't rotate in the y direction
matrix.yy = (FT_Fixed) ( 1 * 0x10000L );
if( m_fakeItal )
{
FT_Matrix matrix;
// Create a 12 degree slant
const float angle = (float)( -M_PI * 12.0f ) / 180.0f;
matrix.xx = (FT_Fixed) ( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed) ( -sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed) ( 0 * 0x10000L ); // Don't rotate in the y direction
matrix.yy = (FT_Fixed) ( 1 * 0x10000L );
FT_Set_Transform( face, &matrix, 0 );
}
FT_Set_Transform( face, &matrix, nullptr );
}
FT_Load_Glyph( face, glyphInfo[i].codepoint, FT_LOAD_NO_BITMAP );
FT_Load_Glyph( face, glyphInfo[i].codepoint, FT_LOAD_NO_BITMAP );
if( m_fakeBold )
FT_Outline_Embolden( &face->glyph->outline, 1 << 6 );
if( m_fakeBold )
FT_Outline_Embolden( &face->glyph->outline, 1 << 6 );
// contours is a collection of all outlines in the glyph; for example the 'o' glyph
// generally contains 2 contours, one for the glyph outline and one for the hole
std::vector<CONTOUR> contours;
OUTLINE_DECOMPOSER decomposer( face->glyph->outline );
OUTLINE_DECOMPOSER decomposer( face->glyph->outline );
if( !decomposer.OutlineToSegments( &glyphData.m_Contours ) )
{
double hb_advance = glyphPos[i].x_advance * GLYPH_SIZE_SCALER;
BOX2D tofuBox( { scaler * 0.03, 0.0 },
{ hb_advance - scaler * 0.02, scaler * 0.72 } );
if( !decomposer.OutlineToSegments( &contours ) )
{
double hb_advance = glyphPos[i].x_advance * GLYPH_SIZE_SCALER;
BOX2D tofuBox( { scaler * 0.03, 0.0 },
{ hb_advance - scaler * 0.02, scaler * 0.72 } );
glyphData.m_Contours.clear();
contours.clear();
CONTOUR outline;
outline.m_Winding = 1;
outline.m_Orientation = FT_ORIENTATION_TRUETYPE;
outline.m_Points.push_back( tofuBox.GetPosition() );
outline.m_Points.push_back( { tofuBox.GetSize().x, tofuBox.GetPosition().y } );
outline.m_Points.push_back( tofuBox.GetSize() );
outline.m_Points.push_back( { tofuBox.GetPosition().x, tofuBox.GetSize().y } );
glyphData.m_Contours.push_back( outline );
CONTOUR outline;
outline.m_Winding = 1;
outline.m_Orientation = FT_ORIENTATION_TRUETYPE;
outline.m_Points.push_back( tofuBox.GetPosition() );
outline.m_Points.push_back( { tofuBox.GetSize().x, tofuBox.GetPosition().y } );
outline.m_Points.push_back( tofuBox.GetSize() );
outline.m_Points.push_back( { tofuBox.GetPosition().x, tofuBox.GetSize().y } );
contours.push_back( outline );
CONTOUR hole;
tofuBox.Move( { scaler * 0.06, scaler * 0.06 } );
tofuBox.SetSize( { tofuBox.GetWidth() - scaler * 0.06,
tofuBox.GetHeight() - scaler * 0.06 } );
hole.m_Winding = 1;
hole.m_Orientation = FT_ORIENTATION_NONE;
hole.m_Points.push_back( tofuBox.GetPosition() );
hole.m_Points.push_back( { tofuBox.GetSize().x, tofuBox.GetPosition().y } );
hole.m_Points.push_back( tofuBox.GetSize() );
hole.m_Points.push_back( { tofuBox.GetPosition().x, tofuBox.GetSize().y } );
contours.push_back( hole );
CONTOUR hole;
tofuBox.Move( { scaler * 0.06, scaler * 0.06 } );
tofuBox.SetSize( { tofuBox.GetWidth() - scaler * 0.06,
tofuBox.GetHeight() - scaler * 0.06 } );
hole.m_Winding = 1;
hole.m_Orientation = FT_ORIENTATION_NONE;
hole.m_Points.push_back( tofuBox.GetPosition() );
hole.m_Points.push_back( { tofuBox.GetSize().x, tofuBox.GetPosition().y } );
hole.m_Points.push_back( tofuBox.GetSize() );
hole.m_Points.push_back( { tofuBox.GetPosition().x, tofuBox.GetSize().y } );
glyphData.m_Contours.push_back( hole );
}
}
std::unique_ptr<OUTLINE_GLYPH> glyph = std::make_unique<OUTLINE_GLYPH>();
std::vector<SHAPE_LINE_CHAIN> holes;
for( CONTOUR& c : contours )
for( CONTOUR& c : glyphData.m_Contours )
{
std::vector<VECTOR2D> points = c.m_Points;
SHAPE_LINE_CHAIN shape;
@ -411,6 +450,16 @@ VECTOR2I OUTLINE_FONT::getTextAsGlyphsUnlocked( BOX2I* aBBox,
}
}
if( glyphData.m_TriangulationData.empty() )
{
glyph->CacheTriangulation( false, false );
glyphData.m_TriangulationData = glyph->GetTriangulationData();
}
else
{
glyph->CacheTriangulation( glyphData.m_TriangulationData );
}
aGlyphs->push_back( std::move( glyph ) );
}

View File

@ -2891,30 +2891,9 @@ void OPENGL_GAL::DrawGlyphs( const std::vector<std::unique_ptr<KIFONT::GLYPH>>&
}
else if( allGlyphsAreOutline )
{
// Optimized path for stroke fonts that pre-reserves glyph triangles.
// Optimized path for outline fonts that pre-reserves glyph triangles.
int triangleCount = 0;
if( aGlyphs.size() > 0 )
{
thread_pool& tp = GetKiCadThreadPool();
tp.push_loop( aGlyphs.size(),
[&]( const int a, const int b)
{
for( int ii = a; ii < b; ++ii )
{
auto glyph = static_cast<KIFONT::OUTLINE_GLYPH*>( aGlyphs.at( ii ).get() );
// Only call CacheTriangulation() if it has never been done before.
// Otherwise we'll hash the triangulation to see if it has been edited,
// and all our glpyh editing ops update the triangulation anyway.
if( glyph->TriangulatedPolyCount() == 0 )
glyph->CacheTriangulation( false );
}
} );
tp.wait_for_tasks();
}
for( const std::unique_ptr<KIFONT::GLYPH>& glyph : aGlyphs )
{
const auto& outlineGlyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( *glyph );

View File

@ -2,7 +2,7 @@
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021-2022 Kicad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 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
@ -82,6 +82,20 @@ public:
void Triangulate( std::function<void( const VECTOR2I& aPt1,
const VECTOR2I& aPt2,
const VECTOR2I& aPt3 )> aCallback ) const;
void CacheTriangulation( bool aPartition = true, bool aSimplify = false ) override;
/**
* @return a set of triangulated polygons from the glyph. CacheTriangulation() will use this
* data as hint data the next time around.
*/
std::vector<std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>> GetTriangulationData() const;
/**
* Cache the triangulation for the glyph from a known set of triangle indexes.
* (See GetTriangulationData() above for more info.)
*/
void CacheTriangulation( std::vector<std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>>& aHintData );
};

View File

@ -2,7 +2,7 @@
* This program source code file is part of KICAD, a free EDA CAD application.
*
* Copyright (C) 2021 Ola Rinta-Koski
* Copyright (C) 2021 Kicad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 Kicad Developers, see AUTHORS.txt for contributors.
*
* Outline font class
*
@ -48,6 +48,15 @@ struct CONTOUR
FT_Orientation m_Orientation;
};
struct GLYPH_DATA
{
std::vector<CONTOUR> m_Contours;
// Cache of the triangulation data. We'll use this as a hint for triangulating the actual
// OUTLINE_GLYPHs.
std::vector<std::unique_ptr<SHAPE_POLY_SET::TRIANGULATED_POLYGON>> m_TriangulationData;
};
class OUTLINE_DECOMPOSER
{

View File

@ -1,7 +1,7 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Modifications Copyright (C) 2018-2023 KiCad Developers
* Modifications Copyright (C) 2018-2024 KiCad Developers
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -64,7 +64,8 @@ public:
m_result( aResult )
{};
bool TesselatePolygon( const SHAPE_LINE_CHAIN& aPoly )
bool TesselatePolygon( const SHAPE_LINE_CHAIN& aPoly,
SHAPE_POLY_SET::TRIANGULATED_POLYGON* aHintData )
{
m_bbox = aPoly.BBox();
m_result.Clear();
@ -82,9 +83,17 @@ public:
firstVertex->updateList();
auto retval = earcutList( firstVertex );
m_vertices.clear();
return retval;
if( aHintData )
{
m_result.Triangles() = aHintData->Triangles();
return true;
}
else
{
auto retval = earcutList( firstVertex );
m_vertices.clear();
return retval;
}
}
private:

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2019 CERN
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
@ -517,7 +517,10 @@ public:
* But not good for Gerbview (1e7 = 10cm), however using a partition is not useful.
* @param aSimplify = force the algorithm to simplify the POLY_SET before triangulating
*/
void CacheTriangulation( bool aPartition = true, bool aSimplify = false );
virtual void CacheTriangulation( bool aPartition = true, bool aSimplify = false )
{
cacheTriangulation( aPartition, aSimplify, nullptr );
}
bool IsTriangulationUpToDate() const;
MD5_HASH GetHash() const;
@ -1428,6 +1431,10 @@ public:
aBuffer.Append( *this );
}
protected:
void cacheTriangulation( bool aPartition, bool aSimplify,
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* aHintData );
private:
enum DROP_TRIANGULATION_FLAG { SINGLETON };
@ -1526,11 +1533,13 @@ private:
MD5_HASH checksum() const;
private:
protected:
std::vector<POLYGON> m_polys;
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>> m_triangulatedPolys;
bool m_triangulationValid = false;
private:
MD5_HASH m_hash;
};

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2019 CERN
* Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2021-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
@ -2981,7 +2981,8 @@ static SHAPE_POLY_SET partitionPolyIntoRegularCellGrid( const SHAPE_POLY_SET& aP
}
void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
void SHAPE_POLY_SET::cacheTriangulation( bool aPartition, bool aSimplify,
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* aHintData )
{
bool recalculate = !m_hash.IsValid();
MD5_HASH hash;
@ -3005,10 +3006,15 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
auto triangulate =
[]( SHAPE_POLY_SET& polySet, int forOutline,
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest )
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>& dest,
std::vector<std::unique_ptr<TRIANGULATED_POLYGON>>* hintData )
{
bool triangulationValid = false;
int pass = 0;
int index = 0;
if( hintData && hintData->size() != (unsigned) polySet.OutlineCount() )
hintData = nullptr;
while( polySet.OutlineCount() > 0 )
{
@ -3021,7 +3027,8 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
// If the tessellation fails, we re-fracture the polygon, which will
// first simplify the system before fracturing and removing the holes
// This may result in multiple, disjoint polygons.
if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front() ) )
if( !tess.TesselatePolygon( polySet.Polygon( 0 ).front(),
hintData ? hintData->at( index ).get() : nullptr ) )
{
++pass;
@ -3033,10 +3040,12 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
break;
triangulationValid = false;
hintData = nullptr;
continue;
}
polySet.DeletePolygon( 0 );
index++;
triangulationValid = true;
}
@ -3067,7 +3076,7 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
// This pushes the triangulation for all polys in partitions
// to be referenced to the ii-th polygon
if( !triangulate( partitions, ii , m_triangulatedPolys ) )
if( !triangulate( partitions, ii , m_triangulatedPolys, aHintData ) )
{
wxLogTrace( TRIANGULATE_TRACE, "Failed to triangulate partitioned polygon %d", ii );
}
@ -3081,7 +3090,7 @@ void SHAPE_POLY_SET::CacheTriangulation( bool aPartition, bool aSimplify )
tmpSet.Fracture( PM_FAST );
if( !triangulate( tmpSet, -1, m_triangulatedPolys ) )
if( !triangulate( tmpSet, -1, m_triangulatedPolys, aHintData ) )
{
wxLogTrace( TRIANGULATE_TRACE, "Failed to triangulate polygon" );
}