KiCad PCB EDA Suite
Loading...
Searching...
No Matches
stroke_params.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 modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "stroke_params.h"
22
23#include <base_units.h>
24#include <charconv>
25#include <string_utils.h>
26#include <render_settings.h>
28#include <geometry/shape.h>
29#include <geometry/shape_rect.h>
32#include <macros.h>
33#include <trigo.h>
34#include <widgets/msgpanel.h>
35
36using namespace STROKEPARAMS_T;
37
38
39const std::map<LINE_STYLE, struct LINE_STYLE_DESC> lineTypeNames = {
40 { LINE_STYLE::SOLID, { _( "Solid" ), BITMAPS::stroke_solid } },
41 { LINE_STYLE::DASH, { _( "Dashed" ), BITMAPS::stroke_dash } },
42 { LINE_STYLE::DOT, { _( "Dotted" ), BITMAPS::stroke_dot } },
43 { LINE_STYLE::DASHDOT, { _( "Dash-Dot" ), BITMAPS::stroke_dashdot } },
44 { LINE_STYLE::DASHDOTDOT, { _( "Dash-Dot-Dot" ), BITMAPS::stroke_dashdotdot } }
45};
46
47
48void STROKE_PARAMS::Stroke( const SHAPE* aShape, LINE_STYLE aLineStyle, int aWidth,
49 const KIGFX::RENDER_SETTINGS* aRenderSettings,
50 const std::function<void( const VECTOR2I& a,
51 const VECTOR2I& b )>& aStroker )
52{
53 double strokes[6] = { aWidth * 1.0, aWidth * 1.0, aWidth * 1.0, aWidth * 1.0, aWidth * 1.0,
54 aWidth * 1.0 };
55 int wrapAround = 6;
56
57 switch( aLineStyle )
58 {
60 strokes[0] = aRenderSettings->GetDashLength( aWidth );
61 strokes[1] = aRenderSettings->GetGapLength( aWidth );
62 wrapAround = 2;
63 break;
64 case LINE_STYLE::DOT:
65 strokes[0] = aRenderSettings->GetDotLength( aWidth );
66 strokes[1] = aRenderSettings->GetGapLength( aWidth );
67 wrapAround = 2;
68 break;
70 strokes[0] = aRenderSettings->GetDashLength( aWidth );
71 strokes[1] = aRenderSettings->GetGapLength( aWidth );
72 strokes[2] = aRenderSettings->GetDotLength( aWidth );
73 strokes[3] = aRenderSettings->GetGapLength( aWidth );
74 wrapAround = 4;
75 break;
77 strokes[0] = aRenderSettings->GetDashLength( aWidth );
78 strokes[1] = aRenderSettings->GetGapLength( aWidth );
79 strokes[2] = aRenderSettings->GetDotLength( aWidth );
80 strokes[3] = aRenderSettings->GetGapLength( aWidth );
81 strokes[4] = aRenderSettings->GetDotLength( aWidth );
82 strokes[5] = aRenderSettings->GetGapLength( aWidth );
83 wrapAround = 6;
84 break;
85 default:
86 UNIMPLEMENTED_FOR( lineTypeNames.at( aLineStyle ).name );
87 }
88
89 switch( aShape->Type() )
90 {
91 case SH_RECT:
92 {
93 SHAPE_LINE_CHAIN outline = static_cast<const SHAPE_RECT*>( aShape )->Outline();
94 std::set<size_t> arcsHandled;
95
96 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
97 {
98 if( outline.IsArcSegment( ii ) )
99 {
100 size_t arcIndex = outline.ArcIndex( ii );
101
102 if( !arcsHandled.contains( arcIndex ) )
103 {
104 arcsHandled.insert( arcIndex );
105 const SHAPE_ARC& arc( outline.Arc( arcIndex ) );
106 STROKE_PARAMS::Stroke( &arc, aLineStyle, aWidth, aRenderSettings, aStroker );
107 }
108 }
109 else
110 {
111 const SEG& seg = outline.GetSegment( ii );
112 SHAPE_SEGMENT line( seg.A, seg.B );
113 STROKE_PARAMS::Stroke( &line, aLineStyle, aWidth, aRenderSettings, aStroker );
114 }
115 }
116
117 for( int jj = 0; jj < (int) outline.ArcCount(); ++jj )
118 {
119 const SHAPE_ARC& arc( outline.Arc( jj ) );
120 STROKE_PARAMS::Stroke( &arc, aLineStyle, aWidth, aRenderSettings, aStroker );
121 }
122
123 break;
124 }
125
126 case SH_SIMPLE:
127 {
128 const SHAPE_SIMPLE* poly = static_cast<const SHAPE_SIMPLE*>( aShape );
129
130 for( size_t ii = 0; ii < poly->GetSegmentCount(); ++ii )
131 {
132 SEG seg = poly->GetSegment( (int) ii );
133 SHAPE_SEGMENT line( seg.A, seg.B );
134 STROKE_PARAMS::Stroke( &line, aLineStyle, aWidth, aRenderSettings, aStroker );
135 }
136
137 break;
138 }
139
140 case SH_SEGMENT:
141 {
142 const SHAPE_SEGMENT* line = static_cast<const SHAPE_SEGMENT*>( aShape );
143
144 VECTOR2D start = line->GetSeg().A;
145 VECTOR2D end = line->GetSeg().B;
146 BOX2I clip( start, VECTOR2I( KiROUND( end.x - start.x ), KiROUND( end.y - start.y ) ) );
147 clip.Normalize();
148
149 double theta = atan2( end.y - start.y, end.x - start.x );
150
151 for( size_t i = 0; i < 10000; ++i )
152 {
153 // Calculations MUST be done in doubles to keep from accumulating rounding
154 // errors as we go.
155 VECTOR2D next( start.x + strokes[ i % wrapAround ] * cos( theta ),
156 start.y + strokes[ i % wrapAround ] * sin( theta ) );
157
158 // Drawing each segment can be done rounded to ints.
159 VECTOR2I a( KiROUND( start.x ), KiROUND( start.y ) );
160 VECTOR2I b( KiROUND( next.x ), KiROUND( next.y ) );
161
162 if( ClipLine( &clip, a.x, a.y, b.x, b.y ) )
163 break;
164 else if( i % 2 == 0 )
165 aStroker( a, b );
166
167 start = next;
168 }
169
170 break;
171 }
172
173 case SH_ARC:
174 {
175 const SHAPE_ARC* arc = static_cast<const SHAPE_ARC*>( aShape );
176
177 double r = arc->GetRadius();
178 double C = 2.0 * M_PI * r;
179 VECTOR2I center = arc->GetCenter();
180 VECTOR2D startRadial( arc->GetP0() - center );
181 EDA_ANGLE startAngle( startRadial );
182 VECTOR2D endRadial( arc->GetP1() - center );
183 EDA_ANGLE arcEndAngle( endRadial );
184
185 if( arcEndAngle == startAngle )
186 arcEndAngle = startAngle + ANGLE_360; // ring, not null
187
188 if( startAngle > arcEndAngle )
189 {
190 if( arcEndAngle < ANGLE_0 )
191 arcEndAngle = arcEndAngle.Normalize();
192 else
193 startAngle = startAngle.Normalize() - ANGLE_360;
194 }
195
196 wxASSERT( startAngle < arcEndAngle );
197
198 EDA_ANGLE angleIncrement = EDA_ANGLE( 0.5, DEGREES_T );
199
200 for( size_t i = 0; i < 10000 && startAngle < arcEndAngle; ++i )
201 {
202 EDA_ANGLE theta = ANGLE_360 * strokes[ i % wrapAround ] / C;
203 EDA_ANGLE endAngle = std::min( startAngle + theta, arcEndAngle );
204
205 if( i % 2 == 0 )
206 {
207 if( ( ( aLineStyle == LINE_STYLE::DASHDOT || aLineStyle == LINE_STYLE::DASHDOTDOT )
208 && i % wrapAround == 0 )
209 || aLineStyle == LINE_STYLE::DASH )
210 {
211 for( EDA_ANGLE currentAngle = startAngle; currentAngle < endAngle;
212 currentAngle += angleIncrement )
213 {
214 VECTOR2I a( center.x + KiROUND( r * currentAngle.Cos() ),
215 center.y + KiROUND( r * currentAngle.Sin() ) );
216
217 // Calculate the next angle step, ensuring it doesn't exceed the endAngle
218 EDA_ANGLE nextAngle = currentAngle + angleIncrement;
219
220 if( nextAngle > endAngle )
221 {
222 nextAngle = endAngle; // Set nextAngle to endAngle if it exceeds
223 }
224
225 VECTOR2I b( center.x + KiROUND( r * nextAngle.Cos() ),
226 center.y + KiROUND( r * nextAngle.Sin() ) );
227
228 aStroker( a, b ); // Draw the segment as an arc
229 }
230 }
231 else
232 {
233 VECTOR2I a( center.x + KiROUND( r * startAngle.Cos() ),
234 center.y + KiROUND( r * startAngle.Sin() ) );
235 VECTOR2I b( center.x + KiROUND( r * endAngle.Cos() ),
236 center.y + KiROUND( r * endAngle.Sin() ) );
237
238 aStroker( a, b );
239 }
240 }
241
242 startAngle = endAngle;
243 }
244
245 break;
246 }
247
248 case SH_CIRCLE:
249 // A circle is always filled; a ring is represented by a 360° arc.
251
252 default:
254 }
255}
256
257
259{
260 wxString token;
261
262 switch( aStyle )
263 {
264 case LINE_STYLE::DASH: token = wxT( "dash" ); break;
265 case LINE_STYLE::DOT: token = wxT( "dot" ); break;
266 case LINE_STYLE::DASHDOT: token = wxT( "dash_dot" ); break;
267 case LINE_STYLE::DASHDOTDOT: token = wxT( "dash_dot_dot" ); break;
268 case LINE_STYLE::SOLID: token = wxT( "solid" ); break;
269 case LINE_STYLE::DEFAULT: token = wxT( "default" ); break;
270 }
271
272 return token;
273}
274
275
277 std::vector<MSG_PANEL_ITEM>& aList,
278 bool aIncludeStyle, bool aIncludeWidth )
279{
280 if( aIncludeStyle )
281 {
282 wxString msg = _( "Default" );
283
284 for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
285 {
286 if( lineStyle == GetLineStyle() )
287 {
288 msg = lineStyleDesc.name;
289 break;
290 }
291 }
292
293 aList.emplace_back( _( "Line Style" ), msg );
294 }
295
296 if( aIncludeWidth )
297 aList.emplace_back( _( "Line Width" ), aUnitsProvider->MessageTextFromValue( GetWidth() ) );
298}
299
300
301void STROKE_PARAMS::Format( OUTPUTFORMATTER* aFormatter, const EDA_IU_SCALE& aIuScale ) const
302{
303 wxASSERT( aFormatter != nullptr );
304
306 {
307 aFormatter->Print( "(stroke (width %s) (type %s))",
308 EDA_UNIT_UTILS::FormatInternalUnits( aIuScale, GetWidth() ).c_str(),
310 }
311 else
312 {
313 aFormatter->Print( "(stroke (width %s) (type %s) (color %d %d %d %s))",
314 EDA_UNIT_UTILS::FormatInternalUnits( aIuScale, GetWidth() ).c_str(),
316 KiROUND( GetColor().r * 255.0 ),
317 KiROUND( GetColor().g * 255.0 ),
318 KiROUND( GetColor().b * 255.0 ),
319 FormatDouble2Str( GetColor().a ).c_str() );
320 }
321}
322
323
325{
326 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
327 {
328 if( token != T_LEFT )
329 Expecting( T_LEFT );
330
331 token = NextTok();
332
333 switch( token )
334 {
335 case T_width:
336 aStroke.SetWidth( KiROUND( parseDouble( "stroke width" ) * m_iuPerMM ) );
337 NeedRIGHT();
338 break;
339
340 case T_type:
341 {
342 token = NextTok();
343
344 switch( token )
345 {
346 case T_dash: aStroke.SetLineStyle( LINE_STYLE::DASH ); break;
347 case T_dot: aStroke.SetLineStyle( LINE_STYLE::DOT ); break;
348 case T_dash_dot: aStroke.SetLineStyle( LINE_STYLE::DASHDOT ); break;
349 case T_dash_dot_dot: aStroke.SetLineStyle( LINE_STYLE::DASHDOTDOT ); break;
350 case T_solid: aStroke.SetLineStyle( LINE_STYLE::SOLID ); break;
351 case T_default: aStroke.SetLineStyle( LINE_STYLE::DEFAULT ); break;
352 default:
353 Expecting( "solid, dash, dash_dot, dash_dot_dot, dot or default" );
354 }
355
356 NeedRIGHT();
357 break;
358 }
359
360 case T_color:
361 {
363
364 color.r = parseInt( "red" ) / 255.0;
365 color.g = parseInt( "green" ) / 255.0;
366 color.b = parseInt( "blue" ) / 255.0;
367 color.a = std::clamp( parseDouble( "alpha" ), 0.0, 1.0 );
368
369 aStroke.SetColor( color );
370 NeedRIGHT();
371 break;
372 }
373
374 default:
375 Expecting( "width, type, or color" );
376 }
377 }
378}
379
380
381int STROKE_PARAMS_PARSER::parseInt( const char* aText )
382{
383 T token = NextTok();
384
385 if( token != T_NUMBER )
386 Expecting( aText );
387
388 return atoi( CurText() );
389}
390
391
392double STROKE_PARAMS_PARSER::parseDouble( const char* aText )
393{
394 T token = NextTok();
395
396 if( token != T_NUMBER )
397 Expecting( aText );
398
399 return DSNLEXER::parseDouble();
400}
401
402
int color
@ stroke_dashdotdot
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:990
constexpr BOX2< Vec > & Normalize()
Ensure that the height and width are positive.
Definition box2.h:146
double parseDouble()
Parse the current token as an ASCII numeric string with possible leading whitespace into a double pre...
Definition dsnlexer.cpp:862
EDA_ANGLE Normalize()
Definition eda_angle.h:229
double Sin() const
Definition eda_angle.h:178
double Cos() const
Definition eda_angle.h:197
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:104
static const COLOR4D UNSPECIFIED
For legacy support; used as a value to indicate color hasn't been set yet.
Definition color4d.h:398
Container for all the knowledge about how graphical objects are drawn on any output surface/device.
double GetGapLength(int aLineWidth) const
double GetDotLength(int aLineWidth) const
double GetDashLength(int aLineWidth) const
An interface used to output 8 bit text in a convenient way.
Definition richio.h:323
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:465
Definition seg.h:42
VECTOR2I A
Definition seg.h:49
VECTOR2I B
Definition seg.h:50
const VECTOR2I & GetP1() const
Definition shape_arc.h:119
double GetRadius() const
const VECTOR2I & GetP0() const
Definition shape_arc.h:118
const VECTOR2I & GetCenter() const
SHAPE_TYPE Type() const
Return the type of the shape.
Definition shape.h:98
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
const SHAPE_ARC & Arc(size_t aArc) const
ssize_t ArcIndex(size_t aSegment) const
Return the arc index for the given segment index.
virtual const SEG GetSegment(int aIndex) const override
int SegmentCount() const
Return the number of segments in this line chain.
size_t ArcCount() const
bool IsArcSegment(size_t aSegment) const
const SEG & GetSeg() const
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
virtual const SEG GetSegment(int aIndex) const override
virtual size_t GetSegmentCount() const override
An abstract shape on 2D plane.
Definition shape.h:126
int parseInt(const char *aText)
void ParseStroke(STROKE_PARAMS &aStroke)
double parseDouble(const char *aText)
Simple container to manage line stroke parameters.
int GetWidth() const
void SetLineStyle(LINE_STYLE aLineStyle)
void SetWidth(int aWidth)
void SetColor(const KIGFX::COLOR4D &aColor)
void GetMsgPanelInfo(UNITS_PROVIDER *aUnitsProvider, std::vector< MSG_PANEL_ITEM > &aList, bool aIncludeStyle=true, bool aIncludeWidth=true)
LINE_STYLE GetLineStyle() const
void Format(OUTPUTFORMATTER *out, const EDA_IU_SCALE &aIuScale) const
KIGFX::COLOR4D GetColor() const
static void Stroke(const SHAPE *aShape, LINE_STYLE aLineStyle, int aWidth, const KIGFX::RENDER_SETTINGS *aRenderSettings, const std::function< void(const VECTOR2I &a, const VECTOR2I &b)> &aStroker)
static wxString GetLineStyleToken(LINE_STYLE aStyle)
wxString MessageTextFromValue(double aValue, bool aAddUnitLabel=true, EDA_DATA_TYPE aType=EDA_DATA_TYPE::DISTANCE) const
A lower-precision version of StringFromValue().
#define _(s)
static constexpr EDA_ANGLE ANGLE_0
Definition eda_angle.h:411
@ DEGREES_T
Definition eda_angle.h:31
static constexpr EDA_ANGLE ANGLE_360
Definition eda_angle.h:417
a few functions useful in geometry calculations.
bool ClipLine(const BOX2I *aClipBox, int &x1, int &y1, int &x2, int &y2)
Test if any part of a line falls within the bounds of a rectangle.
This file contains miscellaneous commonly used macros and functions.
#define KI_FALLTHROUGH
The KI_FALLTHROUGH macro is to be used when switch statement cases should purposely fallthrough from ...
Definition macros.h:83
#define UNIMPLEMENTED_FOR(type)
Definition macros.h:96
Message panel definition file.
KICOMMON_API std::string FormatInternalUnits(const EDA_IU_SCALE &aIuScale, int aValue, EDA_DATA_TYPE aDataType=EDA_DATA_TYPE::DISTANCE)
Converts aValue from internal units to a string appropriate for writing to file.
CITER next(CITER it)
Definition ptree.cpp:124
@ SH_RECT
axis-aligned rectangle
Definition shape.h:47
@ SH_CIRCLE
circle
Definition shape.h:50
@ SH_SIMPLE
simple polygon
Definition shape.h:51
@ SH_SEGMENT
line segment
Definition shape.h:48
@ SH_ARC
circular arc
Definition shape.h:54
static wxString SHAPE_TYPE_asString(SHAPE_TYPE a)
Definition shape.h:59
std::string FormatDouble2Str(double aValue)
Print a float number without using scientific notation and no trailing 0 This function is intended in...
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
Conversion map between LINE_STYLE values and style names displayed.
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
Conversion map between LINE_STYLE values and style names displayed.
LINE_STYLE
Dashed line types.
VECTOR2I center
VECTOR2I end
#define M_PI
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694