KiCad PCB EDA Suite
Loading...
Searching...
No Matches
outline_decomposer.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KICAD, a free EDA CAD application.
3 *
4 * Copyright (C) 2021 Ola Rinta-Koski <[email protected]>
5 * Copyright (C) 2021 Kicad Developers, see AUTHORS.txt for contributors.
6 *
7 * Outline font class
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
28#include <bezier_curves.h>
29
30using namespace KIFONT;
31
33 m_outline( aOutline ),
34 m_contours( nullptr )
35{
36}
37
38
39static VECTOR2D toVector2D( const FT_Vector* aFreeTypeVector )
40{
41 return VECTOR2D( aFreeTypeVector->x * GLYPH_SIZE_SCALER,
42 aFreeTypeVector->y * GLYPH_SIZE_SCALER );
43}
44
45
47{
48 CONTOUR contour;
49 contour.m_Orientation = FT_Outline_Get_Orientation( &m_outline );
50 m_contours->push_back( contour );
51}
52
53
55{
56 // don't add repeated points
57 if( m_contours->back().m_Points.empty() || m_contours->back().m_Points.back() != p )
58 m_contours->back().m_Points.push_back( p );
59}
60
61
62int OUTLINE_DECOMPOSER::moveTo( const FT_Vector* aEndPoint, void* aCallbackData )
63{
64 OUTLINE_DECOMPOSER* decomposer = static_cast<OUTLINE_DECOMPOSER*>( aCallbackData );
65
66 decomposer->m_lastEndPoint = toVector2D( aEndPoint );
67
68 decomposer->newContour();
69 decomposer->addContourPoint( decomposer->m_lastEndPoint );
70
71 return 0;
72}
73
74
75int OUTLINE_DECOMPOSER::lineTo( const FT_Vector* aEndPoint, void* aCallbackData )
76{
77 OUTLINE_DECOMPOSER* decomposer = static_cast<OUTLINE_DECOMPOSER*>( aCallbackData );
78
79 decomposer->m_lastEndPoint = toVector2D( aEndPoint );
80
81 decomposer->addContourPoint( decomposer->m_lastEndPoint );
82
83 return 0;
84}
85
86
87int OUTLINE_DECOMPOSER::quadraticTo( const FT_Vector* aControlPoint, const FT_Vector* aEndPoint,
88 void* aCallbackData )
89{
90 return cubicTo( aControlPoint, nullptr, aEndPoint, aCallbackData );
91}
92
93
94int OUTLINE_DECOMPOSER::cubicTo( const FT_Vector* aFirstControlPoint,
95 const FT_Vector* aSecondControlPoint, const FT_Vector* aEndPoint,
96 void* aCallbackData )
97{
98 OUTLINE_DECOMPOSER* decomposer = static_cast<OUTLINE_DECOMPOSER*>( aCallbackData );
99
100 GLYPH_POINTS bezier;
101 bezier.push_back( decomposer->m_lastEndPoint );
102 bezier.push_back( toVector2D( aFirstControlPoint ) );
103
104 if( aSecondControlPoint )
105 {
106 // aSecondControlPoint == nullptr for quadratic Beziers
107 bezier.push_back( toVector2D( aSecondControlPoint ) );
108 }
109
110 bezier.push_back( toVector2D( aEndPoint ) );
111
112 GLYPH_POINTS result;
113 decomposer->approximateBezierCurve( result, bezier );
114 for( const VECTOR2D& p : result )
115 decomposer->addContourPoint( p );
116
117 decomposer->m_lastEndPoint = toVector2D( aEndPoint );
118
119 return 0;
120}
121
122
124{
125 m_contours = aContours;
126
127 FT_Outline_Funcs callbacks;
128
129 callbacks.move_to = moveTo;
130 callbacks.line_to = lineTo;
131 callbacks.conic_to = quadraticTo;
132 callbacks.cubic_to = cubicTo;
133 callbacks.shift = 0;
134 callbacks.delta = 0;
135
136 FT_Error e = FT_Outline_Decompose( &m_outline, &callbacks, this );
137
138 if( e )
139 {
140 // TODO: handle error != 0
141 }
142
143 for( CONTOUR& c : *m_contours )
144 c.m_Winding = winding( c.m_Points );
145}
146
147
148// use converter in kimath
150 const GLYPH_POINTS& aBezier ) const
151{
152 wxASSERT( aBezier.size() == 3 );
153
154 // BEZIER_POLY only handles cubic Bezier curves, even though the
155 // comments say otherwise...
156 //
157 // Quadratic to cubic Bezier conversion:
158 // cpn = Cubic Bezier control points (n = 0..3, 4 in total)
159 // qpn = Quadratic Bezier control points (n = 0..2, 3 in total)
160 // cp0 = qp0, cp1 = qp0 + 2/3 * (qp1 - qp0), cp2 = qp2 + 2/3 * (qp1 - qp2), cp3 = qp2
161
162 GLYPH_POINTS cubic;
163 cubic.reserve( 4 );
164
165 cubic.push_back( aBezier[0] ); // cp0
166 cubic.push_back( aBezier[0] + ( ( aBezier[1] - aBezier[0] ) * 2 / 3 ) ); // cp1
167 cubic.push_back( aBezier[2] + ( ( aBezier[1] - aBezier[2] ) * 2 / 3 ) ); // cp2
168 cubic.push_back( aBezier[2] ); // cp3
169
170 return approximateCubicBezierCurve( aResult, cubic );
171}
172
173
175 const GLYPH_POINTS& aCubicBezier ) const
176{
177 wxASSERT( aCubicBezier.size() == 4 );
178
179 // minimumSegmentLength defines the "smoothness" of the
180 // curve-to-straight-segments conversion: the larger, the coarser
181 // TODO: find out what the minimum segment length should really be!
182 constexpr int minimumSegmentLength = 10;
183 BEZIER_POLY converter( aCubicBezier );
184 converter.GetPoly( aResult, minimumSegmentLength );
185
186 return true;
187}
188
189
191 const GLYPH_POINTS& aBezier ) const
192{
193 switch( aBezier.size() )
194 {
195 case 4: // cubic
196 return approximateCubicBezierCurve( aResult, aBezier );
197 break;
198 case 3: // quadratic
199 return approximateQuadraticBezierCurve( aResult, aBezier );
200 break;
201 default:
202 // error, only 3 and 4 are acceptable values
203 return false;
204 }
205}
206
207
208int OUTLINE_DECOMPOSER::winding( const GLYPH_POINTS& aContour ) const
209{
210 // -1 == counterclockwise, 1 == clockwise
211
212 const int cw = 1;
213 const int ccw = -1;
214
215 if( aContour.size() < 2 )
216 {
217 // zero or one points, so not a clockwise contour - in fact not a contour at all
218 //
219 // It could also be argued that a contour needs 3 extremum points at a minimum to be
220 // considered a proper contour (ie. a glyph (subpart) outline, or a hole)
221 return 0;
222 }
223
224 double sum = 0.0;
225 size_t len = aContour.size();
226
227 for( size_t i = 0; i < len - 1; i++ )
228 {
229 VECTOR2D p1 = aContour[ i ];
230 VECTOR2D p2 = aContour[ i + 1 ];
231
232 sum += ( ( p2.x - p1.x ) * ( p2.y + p1.y ) );
233 }
234
235 sum += ( ( aContour[0].x - aContour[len - 1].x ) * ( aContour[0].y + aContour[len - 1].y ) );
236
237 if( sum > 0.0 )
238 return cw;
239 if( sum < 0.0 )
240 return ccw;
241
242 return 0;
243}
Bezier curves to polygon converter.
Definition: bezier_curves.h:38
void GetPoly(std::vector< VECTOR2I > &aOutput, int aMinSegLen=0, int aMaxSegCount=32)
Convert a Bezier curve to a polygon.
void addContourPoint(const VECTOR2D &p)
OUTLINE_DECOMPOSER(FT_Outline &aOutline)
static int cubicTo(const FT_Vector *aFirstControlPoint, const FT_Vector *aSecondControlPoint, const FT_Vector *aEndPoint, void *aCallbackData)
static int moveTo(const FT_Vector *aEndPoint, void *aCallbackData)
static int lineTo(const FT_Vector *aEndPoint, void *aCallbackData)
void OutlineToSegments(CONTOURS *aContours)
bool approximateQuadraticBezierCurve(GLYPH_POINTS &result, const GLYPH_POINTS &bezier) const
int winding(const GLYPH_POINTS &aContour) const
static int quadraticTo(const FT_Vector *aControlPoint, const FT_Vector *aEndPoint, void *aCallbackData)
bool approximateCubicBezierCurve(GLYPH_POINTS &result, const GLYPH_POINTS &bezier) const
bool approximateBezierCurve(GLYPH_POINTS &result, const GLYPH_POINTS &bezier) const
std::vector< CONTOUR > CONTOURS
constexpr double GLYPH_SIZE_SCALER
Definition: glyph.h:42
std::vector< VECTOR2D > GLYPH_POINTS
Definition: glyph.h:110
static VECTOR2D toVector2D(const FT_Vector *aFreeTypeVector)
FT_Orientation m_Orientation
VECTOR2< double > VECTOR2D
Definition: vector2d.h:587