KiCad PCB EDA Suite
Loading...
Searching...
No Matches
easypc_text_metrics.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
22#include <algorithm>
23#include <cstdlib>
24#include <limits>
25#include <memory>
26#include <vector>
27
30#include <font/stroke_font.h>
31#include <ki_exception.h>
32#include <wx/strconv.h>
33#include <wx/translation.h>
34
35
36namespace EASYPC
37{
38
39namespace
40{
41
42 // Easy-PC capitals fill this fraction of the text height, the scale from KiCad glyph units
43 constexpr double CAPITAL_HEIGHT = 0.75;
44
45
47 int32_t strokeCharWidthPercent( char aChar, bool aProportional )
48 {
49 if( !aProportional )
50 return 100;
51
52 static std::unique_ptr<KIFONT::STROKE_FONT> font( KIFONT::STROKE_FONT::LoadFont( wxEmptyString ) );
53 static const wxCSConv cp1252( wxFONTENCODING_CP1252 );
54
55 const char bytes[2] = { aChar, '\0' };
56 wxString text( bytes, cp1252 );
57 int index = text.IsEmpty() ? -1 : static_cast<int>( text[0].GetValue() ) - ' ';
58
59 if( index < 0 || index >= static_cast<int>( font->GetGlyphCount() ) )
60 index = '?' - ' ';
61
62 return KiROUND( font->GetGlyphBoundingBox( index ).GetEnd().x * CAPITAL_HEIGHT * 100.0 );
63 }
64
65
67 int32_t narrow( int64_t aValue )
68 {
69 if( aValue < std::numeric_limits<int32_t>::min() || aValue > std::numeric_limits<int32_t>::max() )
70 THROW_IO_ERROR( _( "Easy-PC text metrics are outside the supported range." ) );
71
72 return static_cast<int32_t>( aValue );
73 }
74
75
77 void rotate( int32_t& aX, int32_t& aY, int32_t aAngle, const VECTOR2I& aCentre )
78 {
79 int64_t reach = std::abs( int64_t( aX ) - aCentre.x ) + std::abs( int64_t( aY ) - aCentre.y );
80
81 narrow( std::abs( int64_t( aCentre.x ) ) + reach );
82 narrow( std::abs( int64_t( aCentre.y ) ) + reach );
83 PointRotate( aX, aY, aAngle, aCentre.x, aCentre.y );
84 }
85
86
88 bool flipsUpright( int32_t aAngle )
89 {
90 return aAngle > 135000 && aAngle <= 305000;
91 }
92
93
95 struct EXTENT_BOX
96 {
97 bool Empty = true;
98 int32_t Left = 0;
99 int32_t Bottom = 0;
100 int32_t Right = 0;
101 int32_t Top = 0;
102
103 void Include( int32_t aX, int32_t aY )
104 {
105 Left = Empty ? aX : std::min( Left, aX );
106 Right = Empty ? aX : std::max( Right, aX );
107 Bottom = Empty ? aY : std::min( Bottom, aY );
108 Top = Empty ? aY : std::max( Top, aY );
109 Empty = false;
110 }
111 };
112
113} // namespace
114
115
117 m_height( aStyle.Height ),
118 m_interlinePercent( aStyle.InterlineHeightPercent ),
119 m_charWidthPercent( aStyle.Font ? 100 : aStyle.CharWidthPercent ),
120 m_proportional( !aStyle.Font && aStyle.Proportional ),
121 m_typeFont( dynamic_cast<const SOURCE_TYPE_FONT*>( aStyle.Font ) != nullptr )
122{
123 // TrueType text has no pen width
124 m_penWidth = m_typeFont ? 0 : aStyle.LineWidth;
125}
126
127
129{
130 if( m_typeFont )
131 THROW_IO_ERROR( _( "Easy-PC TrueType text cannot be measured." ) );
132}
133
134
136{
137 return narrow( int64_t( m_height ) * m_interlinePercent / 100 );
138}
139
140
141int32_t TEXT_METRICS::charWidth( char aChar ) const
142{
143 int64_t w = int64_t( m_height ) * strokeCharWidthPercent( aChar, m_proportional ) / 100;
144
145 if( m_charWidthPercent != 100 )
146 w = m_charWidthPercent * w / 100;
147
148 return narrow( w );
149}
150
151
152int32_t TEXT_METRICS::RunWidth( const std::string& aText, size_t aBegin, size_t aEnd ) const
153{
155
156 int64_t w = 0;
157
158 for( size_t i = aBegin; i < aEnd; ++i )
159 w += charWidth( aText[i] );
160
161 return w != 0 ? narrow( w ) : m_height;
162}
163
164
165BOX2I TEXT_METRICS::TextBox( const wxString& aText, const VECTOR2I& aAnchor, int32_t aAngle, bool aMirrored,
166 int aHAlign, int aVAlign ) const
167{
168 const std::string text = Encode( aText );
169 const size_t n = text.size();
170
171 if( n == 0 )
172 return BOX2I();
173
174 // A doubled underscore toggles the overbar and starts a run, a CR, LF,
175 // CRLF or LFCR starts a line, and each run is measured on its own
176 auto bar = [&]( size_t aPos )
177 {
178 return aPos + 1 < n && text[aPos] == '_' && text[aPos + 1] == '_';
179 };
180
181 std::vector<int64_t> widths;
182 size_t end = 0;
183
184 for( bool first = true;; first = false )
185 {
186 if( bar( end ) )
187 end += 2;
188
189 if( end >= n )
190 break;
191
192 size_t begin = end;
193 bool newLine = text[begin] == '\n' || text[begin] == '\r';
194
195 if( newLine )
196 {
197 ++begin;
198
199 if( begin < n && text[begin] != text[begin - 1] && ( text[begin] == '\n' || text[begin] == '\r' ) )
200 ++begin;
201 }
202
203 if( bar( begin ) )
204 begin += 2;
205
206 for( end = begin; end < n && text[end] != '\n' && text[end] != '\r' && !bar( end ); ++end )
207 {
208 }
209
210 // The first run's newline flag is dropped
211 if( first || newLine )
212 widths.push_back( 0 );
213
214 widths.back() = narrow( widths.back() + RunWidth( text, begin, end ) );
215 }
216
217 int64_t baseline = aAnchor.y;
218
219 if( aVAlign == TEXT_V_TOP )
220 baseline -= m_height;
221 else if( aVAlign == TEXT_V_MIDDLE )
222 baseline -= m_height >> 1;
223
224 EXTENT_BOX box;
225
226 for( size_t i = 0; i < widths.size(); ++i )
227 {
228 if( i > 0 )
229 baseline -= linePitch();
230
231 int64_t x = aAnchor.x;
232
233 if( aHAlign == TEXT_H_RIGHT )
234 x -= widths[i];
235 else if( aHAlign == TEXT_H_CENTRE )
236 x -= widths[i] >> 1;
237
238 box.Include( narrow( x ), narrow( baseline ) );
239 box.Include( narrow( x + widths[i] ), narrow( baseline + linePitch() ) );
240 }
241
242 int64_t grow = ( int64_t( m_penWidth ) + 1 ) >> 1;
243 box.Left = narrow( box.Left - grow );
244 box.Bottom = narrow( box.Bottom - grow );
245 box.Right = narrow( box.Right + grow );
246 box.Top = narrow( box.Top + grow );
247
248 if( aMirrored )
249 {
250 int32_t left = narrow( 2 * int64_t( aAnchor.x ) - box.Right );
251 box.Right = narrow( 2 * int64_t( aAnchor.x ) - box.Left );
252 box.Left = left;
253 }
254
255 if( aAngle != 0 )
256 {
257 // The rotated box bounds the four rotated corners
258 const VECTOR2I corners[4] = {
259 { box.Right, box.Top }, { box.Left, box.Bottom }, { box.Right, box.Bottom }, { box.Left, box.Top }
260 };
261 EXTENT_BOX rotated;
262
263 for( VECTOR2I c : corners )
264 {
265 rotate( c.x, c.y, aAngle, aAnchor );
266 rotated.Include( c.x, c.y );
267 }
268
269 box = rotated;
270 }
271
272 BOX2I out( VECTOR2I( box.Left, box.Bottom ) );
273 out.SetEnd( VECTOR2I( box.Right, box.Top ) );
274 return out;
275}
276
277
278VECTOR2I TEXT_METRICS::UprightAnchor( const wxString& aText, const VECTOR2I& aAnchor, int32_t aAngle, bool aMirrored,
279 int aHAlign, bool aKeepUpright ) const
280{
281 if( !aKeepUpright || aText.IsEmpty() || !flipsUpright( aAngle ) )
282 return aAnchor;
283
284 BOX2I box = TextBox( aText, aAnchor, 0, false, aHAlign );
285
286 // Shrunk by half the pen width, after TextBox grew it by half rounded up
287 int32_t shrink = m_penWidth / 2;
288 int32_t x = narrow( aHAlign == TEXT_H_LEFT ? int64_t( box.GetEnd().x ) - shrink
289 : int64_t( box.GetOrigin().x ) + shrink );
290 int32_t y = narrow( int64_t( m_height ) + box.GetOrigin().y + shrink );
291
292 if( aMirrored )
293 x = narrow( 2 * int64_t( aAnchor.x ) - x );
294
295 rotate( x, y, aAngle, aAnchor );
296 return VECTOR2I( x, y );
297}
298
299
300std::string TEXT_METRICS::Encode( const wxString& aText )
301{
302 static const wxCSConv cp1252( wxFONTENCODING_CP1252 );
303 std::string bytes;
304
305 bytes.reserve( aText.length() );
306
307 for( wxUniChar ch : aText )
308 {
309 if( ch.IsAscii() )
310 {
311 bytes.push_back( static_cast<char>( ch.GetValue() ) );
312 continue;
313 }
314
315 wxCharBuffer one = wxString( ch ).mb_str( cp1252 );
316 bytes.push_back( one.length() == 1 ? one.data()[0] : '\0' );
317 }
318
319 return bytes;
320}
321
322
323bool TextStaysUpright( const SOURCE_DESIGN& aDesign, bool aSchematic )
324{
325 if( aDesign.AdjustTextRotation )
326 return true;
327
328 if( !aSchematic )
329 return false;
330
331 // Schematics before 3000 store no flag and are always upright
332 return aDesign.Version <= 2999 || aDesign.Product == PRODUCT_DESIGNSPARK_CREATOR
333 || aDesign.Product == PRODUCT_DESIGNSPARK_PRO || aDesign.Product == PRODUCT_PROTOPCB;
334}
335
336
337int32_t UprightRotation( int32_t aAngle, bool aKeepUpright )
338{
339 if( !aKeepUpright || !flipsUpright( aAngle ) )
340 return aAngle;
341
342 return ( aAngle + 180000 ) % ANGLE_FULL_TURN;
343}
344
345
346wxString ToKiCadMarkup( const wxString& aText )
347{
348 wxString out;
349 bool overbar = false;
350
351 for( size_t i = 0; i < aText.length(); ++i )
352 {
353 wxUniChar c = aText[i];
354 wxUniChar next = i + 1 < aText.length() ? aText[i + 1] : wxUniChar( 0 );
355
356 if( c == '_' && next == '_' )
357 {
358 out += overbar ? wxS( "}" ) : wxS( "~{" );
359 overbar = !overbar;
360 ++i;
361 }
362 else if( c == '\n' || c == '\r' )
363 {
364 if( overbar )
365 out += wxS( "}" );
366
367 overbar = false;
368 out += c;
369 }
370 else if( c == '~' && next == '{' )
371 {
372 // An empty overbar between them keeps KiCad from reading the pair as markup
373 out += wxS( "~~{}{" );
374 ++i;
375 }
376 else if( c == '}' && overbar )
377 {
378 // A brace would close the overbar, so it is drawn between two overbarred runs
379 out += wxS( "}}~{" );
380 }
381 else
382 {
383 out += c;
384 }
385 }
386
387 if( overbar )
388 out += wxS( "}" );
389
390 return out;
391}
392
393} // namespace EASYPC
int index
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
constexpr const Vec GetEnd() const
Definition box2.h:209
constexpr const Vec & GetOrigin() const
Definition box2.h:207
constexpr void SetEnd(coord_type x, coord_type y)
Definition box2.h:294
int32_t RunWidth(const std::string &aText, size_t aBegin, size_t aEnd) const
Width of bytes [aBegin, aEnd); a run that measures 0 is one height wide.
static std::string Encode(const wxString &aText)
The CP1252 bytes of aText; characters CP1252 lacks become 0.
int32_t charWidth(char aChar) const
TEXT_METRICS(const SOURCE_TEXT_STYLE &aStyle)
BOX2I TextBox(const wxString &aText, const VECTOR2I &aAnchor, int32_t aAngle, bool aMirrored, int aHAlign, int aVAlign=TEXT_V_BASELINE) const
Every line's cell, inflated by half the pen width, mirrored about the anchor and bounded after rotati...
VECTOR2I UprightAnchor(const wxString &aText, const VECTOR2I &aAnchor, int32_t aAngle, bool aMirrored, int aHAlign, bool aKeepUpright) const
When aKeepUpright and UprightRotation turns the text, the anchor moves to the far end of the first li...
static STROKE_FONT * LoadFont(const wxString &aFontName)
Load a stroke font.
The design document and the design parameter tables the importers use.
Easy-PC's text measurement, so text lands where Easy-PC draws it.
Design units of 100 nm and angles of thousandths of a degree, anticlockwise from +X with Y up.
#define _(s)
#define THROW_IO_ERROR(msg)
macro which captures the "call site" values of FILE_, __FUNCTION & LINE
bool TextStaysUpright(const SOURCE_DESIGN &aDesign, bool aSchematic)
Whether aDesign's text is drawn upright: the design's flag, which old schematics imply,...
int32_t UprightRotation(int32_t aAngle, bool aKeepUpright)
With text kept upright, an angle above 135 and up to 305 degrees turns by 180.
void PointRotate(double aPx, double aPy, int32_t aAngle, double aCx, double aCy, int32_t &aOutX, int32_t &aOutY)
Rotate aPx, aPy anticlockwise by aAngle about aCx, aCy as Easy-PC places points: in double with exact...
@ PRODUCT_DESIGNSPARK_CREATOR
@ PRODUCT_DESIGNSPARK_PRO
wxString ToKiCadMarkup(const wxString &aText)
A displayed string in KiCad's markup.
constexpr int ANGLE_FULL_TURN
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
CITER next(CITER it)
Definition ptree.cpp:120
The document: OLE items, then the design.
A TrueType font, which the importers cannot measure.
VECTOR2I end
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708