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 (C) 2021-2022 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, const VECTOR2I& b )>& aStroker )
51{
52 double strokes[6] = { aWidth * 1.0, aWidth * 1.0, aWidth * 1.0, aWidth * 1.0, aWidth * 1.0,
53 aWidth * 1.0 };
54 int wrapAround = 6;
55
56 switch( aLineStyle )
57 {
58 case LINE_STYLE::DASH:
59 strokes[0] = aRenderSettings->GetDashLength( aWidth );
60 strokes[1] = aRenderSettings->GetGapLength( aWidth );
61 wrapAround = 2;
62 break;
63 case LINE_STYLE::DOT:
64 strokes[0] = aRenderSettings->GetDotLength( aWidth );
65 strokes[1] = aRenderSettings->GetGapLength( aWidth );
66 wrapAround = 2;
67 break;
68 case LINE_STYLE::DASHDOT:
69 strokes[0] = aRenderSettings->GetDashLength( aWidth );
70 strokes[1] = aRenderSettings->GetGapLength( aWidth );
71 strokes[2] = aRenderSettings->GetDotLength( aWidth );
72 strokes[3] = aRenderSettings->GetGapLength( aWidth );
73 wrapAround = 4;
74 break;
75 case LINE_STYLE::DASHDOTDOT:
76 strokes[0] = aRenderSettings->GetDashLength( aWidth );
77 strokes[1] = aRenderSettings->GetGapLength( aWidth );
78 strokes[2] = aRenderSettings->GetDotLength( aWidth );
79 strokes[3] = aRenderSettings->GetGapLength( aWidth );
80 strokes[4] = aRenderSettings->GetDotLength( aWidth );
81 strokes[5] = aRenderSettings->GetGapLength( aWidth );
82 wrapAround = 6;
83 break;
84 default:
85 UNIMPLEMENTED_FOR( lineTypeNames.at( aLineStyle ).name );
86 }
87
88 switch( aShape->Type() )
89 {
90 case SH_RECT:
91 {
92 SHAPE_LINE_CHAIN outline = static_cast<const SHAPE_RECT*>( aShape )->Outline();
93
94 for( int ii = 0; ii < outline.SegmentCount(); ++ii )
95 {
96 SEG seg = outline.GetSegment( ii );
97 SHAPE_SEGMENT line( seg.A, seg.B );
98 STROKE_PARAMS::Stroke( &line, aLineStyle, aWidth, aRenderSettings, aStroker );
99 }
100
101 break;
102 }
103
104 case SH_SIMPLE:
105 {
106 const SHAPE_SIMPLE* poly = static_cast<const SHAPE_SIMPLE*>( aShape );
107
108 for( size_t ii = 0; ii < poly->GetSegmentCount(); ++ii )
109 {
110 SEG seg = poly->GetSegment( (int) ii );
111 SHAPE_SEGMENT line( seg.A, seg.B );
112 STROKE_PARAMS::Stroke( &line, aLineStyle, aWidth, aRenderSettings, aStroker );
113 }
114
115 break;
116 }
117
118 case SH_SEGMENT:
119 {
120 const SHAPE_SEGMENT* line = static_cast<const SHAPE_SEGMENT*>( aShape );
121
122 VECTOR2D start = line->GetSeg().A;
123 VECTOR2D end = line->GetSeg().B;
124 BOX2I clip( start, VECTOR2I( KiROUND( end.x - start.x ), KiROUND( end.y - start.y ) ) );
125 clip.Normalize();
126
127 double theta = atan2( end.y - start.y, end.x - start.x );
128
129 for( size_t i = 0; i < 10000; ++i )
130 {
131 // Calculations MUST be done in doubles to keep from accumulating rounding
132 // errors as we go.
133 VECTOR2D next( start.x + strokes[ i % wrapAround ] * cos( theta ),
134 start.y + strokes[ i % wrapAround ] * sin( theta ) );
135
136 // Drawing each segment can be done rounded to ints.
137 VECTOR2I a( KiROUND( start.x ), KiROUND( start.y ) );
138 VECTOR2I b( KiROUND( next.x ), KiROUND( next.y ) );
139
140 if( ClipLine( &clip, a.x, a.y, b.x, b.y ) )
141 break;
142 else if( i % 2 == 0 )
143 aStroker( a, b );
144
145 start = next;
146 }
147
148 break;
149 }
150
151 case SH_ARC:
152 {
153 const SHAPE_ARC* arc = static_cast<const SHAPE_ARC*>( aShape );
154
155 double r = arc->GetRadius();
156 double C = 2.0 * M_PI * r;
157 VECTOR2I center = arc->GetCenter();
158 VECTOR2D startRadial( arc->GetP0() - center );
159 EDA_ANGLE startAngle( startRadial );
160 VECTOR2D endRadial( arc->GetP1() - center );
161 EDA_ANGLE arcEndAngle( endRadial );
162
163 if( arcEndAngle == startAngle )
164 arcEndAngle = startAngle + ANGLE_360; // ring, not null
165
166 if( startAngle > arcEndAngle )
167 {
168 if( arcEndAngle < ANGLE_0 )
169 arcEndAngle = arcEndAngle.Normalize();
170 else
171 startAngle = startAngle.Normalize() - ANGLE_360;
172 }
173
174 wxASSERT( startAngle < arcEndAngle );
175
176 EDA_ANGLE angleIncrementInRadians = EDA_ANGLE( M_PI / ANGLE_360 );
177
178 for( size_t i = 0; i < 10000 && startAngle < arcEndAngle; ++i )
179 {
180 EDA_ANGLE theta = ANGLE_360 * strokes[ i % wrapAround ] / C;
181 EDA_ANGLE endAngle = std::min( startAngle + theta, arcEndAngle );
182
183 if( i % 2 == 0 )
184 {
185 if( ( ( aLineStyle == LINE_STYLE::DASHDOT || aLineStyle == LINE_STYLE::DASHDOTDOT )
186 && i % wrapAround == 0 )
187 || aLineStyle == LINE_STYLE::DASH )
188 {
189 for( EDA_ANGLE currentAngle = startAngle; currentAngle < endAngle;
190 currentAngle += angleIncrementInRadians )
191 {
192 VECTOR2I a( center.x + KiROUND( r * currentAngle.Cos() ),
193 center.y + KiROUND( r * currentAngle.Sin() ) );
194
195 // Calculate the next angle step, ensuring it doesn't exceed the endAngle
196 EDA_ANGLE nextAngle = currentAngle + angleIncrementInRadians;
197
198 if( nextAngle > endAngle )
199 {
200 nextAngle = endAngle; // Set nextAngle to endAngle if it exceeds
201 }
202
203 VECTOR2I b( center.x + KiROUND( r * nextAngle.Cos() ),
204 center.y + KiROUND( r * nextAngle.Sin() ) );
205
206 aStroker( a, b ); // Draw the segment as an arc
207 }
208 }
209 else
210 {
211 VECTOR2I a( center.x + KiROUND( r * startAngle.Cos() ),
212 center.y + KiROUND( r * startAngle.Sin() ) );
213 VECTOR2I b( center.x + KiROUND( r * endAngle.Cos() ),
214 center.y + KiROUND( r * endAngle.Sin() ) );
215
216 aStroker( a, b );
217 }
218 }
219
220 startAngle = endAngle;
221 }
222
223 break;
224 }
225
226 case SH_CIRCLE:
227 // A circle is always filled; a ring is represented by a 360° arc.
229
230 default:
232 }
233}
234
235
237{
238 wxString token;
239
240 switch( aStyle )
241 {
242 case LINE_STYLE::DASH: token = wxT( "dash" ); break;
243 case LINE_STYLE::DOT: token = wxT( "dot" ); break;
244 case LINE_STYLE::DASHDOT: token = wxT( "dash_dot" ); break;
245 case LINE_STYLE::DASHDOTDOT: token = wxT( "dash_dot_dot" ); break;
246 case LINE_STYLE::SOLID: token = wxT( "solid" ); break;
247 case LINE_STYLE::DEFAULT: token = wxT( "default" ); break;
248 }
249
250 return token;
251}
252
253
255 std::vector<MSG_PANEL_ITEM>& aList,
256 bool aIncludeStyle, bool aIncludeWidth )
257{
258 if( aIncludeStyle )
259 {
260 wxString msg = _( "Default" );
261
262 for( const auto& [ lineStyle, lineStyleDesc ] : lineTypeNames )
263 {
264 if( lineStyle == GetLineStyle() )
265 {
266 msg = lineStyleDesc.name;
267 break;
268 }
269 }
270
271 aList.emplace_back( _( "Line Style" ), msg );
272 }
273
274 if( aIncludeWidth )
275 aList.emplace_back( _( "Line Width" ), aUnitsProvider->MessageTextFromValue( GetWidth() ) );
276}
277
278
279void STROKE_PARAMS::Format( OUTPUTFORMATTER* aFormatter, const EDA_IU_SCALE& aIuScale,
280 int aNestLevel ) const
281{
282 wxASSERT( aFormatter != nullptr );
283
285 {
286 aFormatter->Print( aNestLevel, "(stroke (width %s) (type %s))",
287 EDA_UNIT_UTILS::FormatInternalUnits( aIuScale, GetWidth() ).c_str(),
289 }
290 else
291 {
292 aFormatter->Print( aNestLevel, "(stroke (width %s) (type %s) (color %d %d %d %s))",
293 EDA_UNIT_UTILS::FormatInternalUnits( aIuScale, GetWidth() ).c_str(),
295 KiROUND( GetColor().r * 255.0 ),
296 KiROUND( GetColor().g * 255.0 ),
297 KiROUND( GetColor().b * 255.0 ),
298 FormatDouble2Str( GetColor().a ).c_str() );
299 }
300}
301
302
304{
305 for( T token = NextTok(); token != T_RIGHT; token = NextTok() )
306 {
307 if( token != T_LEFT )
308 Expecting( T_LEFT );
309
310 token = NextTok();
311
312 switch( token )
313 {
314 case T_width:
315 aStroke.SetWidth( KiROUND( parseDouble( "stroke width" ) * m_iuPerMM ) );
316 NeedRIGHT();
317 break;
318
319 case T_type:
320 {
321 token = NextTok();
322
323 switch( token )
324 {
325 case T_dash: aStroke.SetLineStyle( LINE_STYLE::DASH ); break;
326 case T_dot: aStroke.SetLineStyle( LINE_STYLE::DOT ); break;
327 case T_dash_dot: aStroke.SetLineStyle( LINE_STYLE::DASHDOT ); break;
328 case T_dash_dot_dot: aStroke.SetLineStyle( LINE_STYLE::DASHDOTDOT ); break;
329 case T_solid: aStroke.SetLineStyle( LINE_STYLE::SOLID ); break;
330 case T_default: aStroke.SetLineStyle( LINE_STYLE::DEFAULT ); break;
331 default:
332 Expecting( "solid, dash, dash_dot, dash_dot_dot, dot or default" );
333 }
334
335 NeedRIGHT();
336 break;
337 }
338
339 case T_color:
340 {
342
343 color.r = parseInt( "red" ) / 255.0;
344 color.g = parseInt( "green" ) / 255.0;
345 color.b = parseInt( "blue" ) / 255.0;
346 color.a = std::clamp( parseDouble( "alpha" ), 0.0, 1.0 );
347
348 aStroke.SetColor( color );
349 NeedRIGHT();
350 break;
351 }
352
353 default:
354 Expecting( "width, type, or color" );
355 }
356 }
357}
358
359
360int STROKE_PARAMS_PARSER::parseInt( const char* aText )
361{
362 T token = NextTok();
363
364 if( token != T_NUMBER )
365 Expecting( aText );
366
367 return atoi( CurText() );
368}
369
370
371double STROKE_PARAMS_PARSER::parseDouble( const char* aText )
372{
373 T token = NextTok();
374
375 if( token != T_NUMBER )
376 Expecting( aText );
377
378 return DSNLEXER::parseDouble();
379}
380
381
int color
Definition: DXF_plotter.cpp:58
@ stroke_dashdotdot
@ stroke_dashdot
@ stroke_solid
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:844
EDA_ANGLE Normalize()
Definition: eda_angle.h:221
double Sin() const
Definition: eda_angle.h:170
double Cos() const
Definition: eda_angle.h:189
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:322
int PRINTF_FUNC Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition: richio.cpp:458
Definition: seg.h:42
VECTOR2I A
Definition: seg.h:49
VECTOR2I B
Definition: seg.h:50
const VECTOR2I & GetP1() const
Definition: shape_arc.h:115
double GetRadius() const
Definition: shape_arc.cpp:554
const VECTOR2I & GetP0() const
Definition: shape_arc.h:114
const VECTOR2I & GetCenter() const
Definition: shape_arc.cpp:523
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...
virtual const SEG GetSegment(int aIndex) const override
int SegmentCount() const
Return the number of segments in this line chain.
const SEG & GetSeg() const
Represent a simple polygon consisting of a zero-thickness closed chain of connected line segments.
Definition: shape_simple.h:42
virtual const SEG GetSegment(int aIndex) const override
Definition: shape_simple.h:174
virtual size_t GetSegmentCount() const override
Definition: shape_simple.h:176
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.
Definition: stroke_params.h:79
int GetWidth() const
Definition: stroke_params.h:89
void SetLineStyle(LINE_STYLE aLineStyle)
Definition: stroke_params.h:93
void SetWidth(int aWidth)
Definition: stroke_params.h:90
void SetColor(const KIGFX::COLOR4D &aColor)
Definition: stroke_params.h:96
void GetMsgPanelInfo(UNITS_PROVIDER *aUnitsProvider, std::vector< MSG_PANEL_ITEM > &aList, bool aIncludeStyle=true, bool aIncludeWidth=true)
LINE_STYLE GetLineStyle() const
Definition: stroke_params.h:92
KIGFX::COLOR4D GetColor() const
Definition: stroke_params.h:95
void Format(OUTPUTFORMATTER *out, const EDA_IU_SCALE &aIuScale, int nestLevel) 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:401
static constexpr EDA_ANGLE ANGLE_360
Definition: eda_angle.h:407
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)
Converts aValue from internal units to a string appropriate for writing to file.
Definition: eda_units.cpp:169
CITER next(CITER it)
Definition: ptree.cpp:126
@ 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.
Definition: string_utils.h:398
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
const std::map< LINE_STYLE, struct LINE_STYLE_DESC > lineTypeNames
LINE_STYLE
Dashed line types.
Definition: stroke_params.h:46
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:691