KiCad PCB EDA Suite
Loading...
Searching...
No Matches
cairo_gal.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) 2012 Torsten Hueter, torstenhtr <at> gmx.de
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 * Copyright (C) 2017-2018 CERN
7 *
8 * @author Maciej Suminski <[email protected]>
9 *
10 * CairoGal - Graphics Abstraction Layer for Cairo
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 */
25
26#include <wx/image.h>
27#include <wx/log.h>
28#include <wx/dcclient.h>
29
30#include <gal/cairo/cairo_gal.h>
32#include <gal/definitions.h>
34#include <font/glyph_types.h>
35#include <math/vector2wx.h>
36#include <math/util.h> // for KiROUND
37#include <trigo.h>
38#include <bitmap_base.h>
39
40#include <algorithm>
41#include <cmath>
42#include <limits>
43#include <ranges>
44
45#include <pixman.h>
46
47using namespace KIGFX;
48
49
50CAIRO_GAL_BASE::CAIRO_GAL_BASE( GAL_DISPLAY_OPTIONS& aDisplayOptions ) : GAL( aDisplayOptions )
51{
52 // Initialise grouping
53 m_isGrouping = false;
54 m_isElementAdded = false;
56 m_currentGroup = nullptr;
57
58 m_lineWidth = 1.0;
60 m_lineWidthIsOdd = true;
61
62 // Initialise Cairo state
63 cairo_matrix_init_identity( &m_cairoWorldScreenMatrix );
64 m_currentContext = nullptr;
65 m_context = nullptr;
66 m_surface = nullptr;
67
68 // Grid color settings are different in Cairo and OpenGL
69 SetGridColor( COLOR4D( 0.1, 0.1, 0.1, 0.8 ) );
71
72 // Avoid uninitialized variables:
73 cairo_matrix_init_identity( &m_currentXform );
74 cairo_matrix_init_identity( &m_currentWorld2Screen );
75}
76
77
79{
80 ClearCache();
81
82 if( m_surface )
83 cairo_surface_destroy( m_surface );
84
85 if( m_context )
86 cairo_destroy( m_context );
87
88 for( _cairo_surface* imageSurface : m_imageSurfaces )
89 cairo_surface_destroy( imageSurface );
90}
91
92
97
98
100{
101 // Force remaining objects to be drawn
102 Flush();
103}
104
105
110
111
112const VECTOR2D CAIRO_GAL_BASE::xform( double x, double y )
113{
114 VECTOR2D rv;
115
118 return rv;
119}
120
121
123{
124 return xform( aP.x, aP.y );
125}
126
127
128double CAIRO_GAL_BASE::angle_xform( double aAngle )
129{
130 // calculate rotation angle due to the rotation transform
131 // and if flipped on X axis.
132 double world_rotation = -std::atan2( m_currentWorld2Screen.xy, m_currentWorld2Screen.xx );
133
134 // When flipped on X axis, the rotation angle is M_PI - initial angle:
135 if( IsFlippedX() )
136 world_rotation = M_PI - world_rotation;
137
138 return std::fmod( aAngle + world_rotation, 2.0 * M_PI );
139}
140
141
142void CAIRO_GAL_BASE::arc_angles_xform_and_normalize( double& aStartAngle, double& aEndAngle )
143{
144 // 360 deg arcs have a specific calculation.
145 bool is_360deg_arc = std::abs( aEndAngle - aStartAngle ) >= 2 * M_PI;
146 double startAngle = aStartAngle;
147 double endAngle = aEndAngle;
148
149 // When the view is flipped, the coordinates are flipped by the matrix transform
150 // However, arc angles need to be "flipped": the flipped angle is M_PI - initial angle.
151 if( IsFlippedX() )
152 {
153 startAngle = M_PI - startAngle;
154 endAngle = M_PI - endAngle;
155 }
156
157 // Normalize arc angles
158 normalize( startAngle, endAngle );
159
160 // now rotate arc according to the rotation transform matrix
161 // Remark:
162 // We call angle_xform() to calculate angles according to the flip/rotation
163 // transform and normalize between -2M_PI and +2M_PI.
164 // Therefore, if aStartAngle = aEndAngle + 2*n*M_PI, the transform gives
165 // aEndAngle = aStartAngle
166 // So, if this is the case, force the aEndAngle value to draw a circle.
167 aStartAngle = angle_xform( startAngle );
168
169 if( is_360deg_arc ) // arc is a full circle
170 aEndAngle = aStartAngle + 2 * M_PI;
171 else
172 aEndAngle = angle_xform( endAngle );
173}
174
175
176double CAIRO_GAL_BASE::xform( double x )
177{
178 double dx = m_currentWorld2Screen.xx * x;
179 double dy = m_currentWorld2Screen.yx * x;
180 return sqrt( dx * dx + dy * dy );
181}
182
183
184static double roundp( double x )
185{
186 return floor( x + 0.5 ) + 0.5;
187}
188
189
191{
192 if( m_lineWidthIsOdd )
193 return VECTOR2D( ::roundp( v.x ), ::roundp( v.y ) );
194 else
195 return VECTOR2D( floor( v.x + 0.5 ), floor( v.y + 0.5 ) );
196}
197
198
199void CAIRO_GAL_BASE::DrawLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
200{
202
203 VECTOR2D p0 = roundp( xform( aStartPoint ) );
204 VECTOR2D p1 = roundp( xform( aEndPoint ) );
205
206 cairo_move_to( m_currentContext, p0.x, p0.y );
207 cairo_line_to( m_currentContext, p1.x, p1.y );
208 flushPath();
209 m_isElementAdded = true;
210}
211
212
213void CAIRO_GAL_BASE::syncLineWidth( bool aForceWidth, double aWidth )
214{
215 double w = floor( xform( aForceWidth ? aWidth : m_lineWidth ) + 0.5 );
216
217 if( w <= 1.0 )
218 {
219 w = 1.0;
220 cairo_set_line_join( m_currentContext, CAIRO_LINE_JOIN_MITER );
221 cairo_set_line_cap( m_currentContext, CAIRO_LINE_CAP_BUTT );
222 cairo_set_line_width( m_currentContext, 1.0 );
223 m_lineWidthIsOdd = true;
224 }
225 else
226 {
227 cairo_set_line_join( m_currentContext, CAIRO_LINE_JOIN_ROUND );
228 cairo_set_line_cap( m_currentContext, CAIRO_LINE_CAP_ROUND );
229 cairo_set_line_width( m_currentContext, w );
230 m_lineWidthIsOdd = ( (int) w % 2 ) == 1;
231 }
232
234}
235
236
237void CAIRO_GAL_BASE::DrawSegmentChain( const std::vector<VECTOR2D>& aPointList, double aWidth )
238{
239 for( size_t i = 0; i + 1 < aPointList.size(); ++i )
240 DrawSegment( aPointList[i], aPointList[i + 1], aWidth );
241}
242
243
244void CAIRO_GAL_BASE::DrawSegmentChain( const SHAPE_LINE_CHAIN& aLineChain, double aWidth )
245{
246 int numPoints = aLineChain.PointCount();
247
248 if( aLineChain.IsClosed() )
249 numPoints += 1;
250
251 for( int i = 0; i + 1 < numPoints; ++i )
252 DrawSegment( aLineChain.CPoint( i ), aLineChain.CPoint( i + 1 ), aWidth );
253}
254
255
256void CAIRO_GAL_BASE::DrawSegment( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
257 double aWidth )
258{
259 if( m_isFillEnabled )
260 {
261 syncLineWidth( true, aWidth );
262
263 VECTOR2D p0 = roundp( xform( aStartPoint ) );
264 VECTOR2D p1 = roundp( xform( aEndPoint ) );
265
266 cairo_move_to( m_currentContext, p0.x, p0.y );
267 cairo_line_to( m_currentContext, p1.x, p1.y );
268 cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g, m_fillColor.b,
269 m_fillColor.a );
270 cairo_stroke( m_currentContext );
271 }
272 else
273 {
274 aWidth /= 2.0;
275 SetLineWidth( 1.0 );
277
278 // Outline mode for tracks
279 VECTOR2D startEndVector = aEndPoint - aStartPoint;
280 double lineAngle = atan2( startEndVector.y, startEndVector.x );
281
282 double sa = sin( lineAngle + M_PI / 2.0 );
283 double ca = cos( lineAngle + M_PI / 2.0 );
284
285 VECTOR2D pa0 = xform( aStartPoint + VECTOR2D( aWidth * ca, aWidth * sa ) );
286 VECTOR2D pa1 = xform( aStartPoint - VECTOR2D( aWidth * ca, aWidth * sa ) );
287 VECTOR2D pb0 = xform( aEndPoint + VECTOR2D( aWidth * ca, aWidth * sa ) );
288 VECTOR2D pb1 = xform( aEndPoint - VECTOR2D( aWidth * ca, aWidth * sa ) );
289
290 cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g, m_strokeColor.b,
291 m_strokeColor.a );
292
293 cairo_move_to( m_currentContext, pa0.x, pa0.y );
294 cairo_line_to( m_currentContext, pb0.x, pb0.y );
295
296 cairo_move_to( m_currentContext, pa1.x, pa1.y );
297 cairo_line_to( m_currentContext, pb1.x, pb1.y );
298 flushPath();
299
300 // Calculate the segment angle and arc center in normal/mirrored transform for rounded ends.
301 VECTOR2D center_a = xform( aStartPoint );
302 VECTOR2D center_b = xform( aEndPoint );
303 startEndVector = center_b - center_a;
304 lineAngle = atan2( startEndVector.y, startEndVector.x );
305 double radius = ( pa0 - center_a ).EuclideanNorm();
306
307 // Draw the rounded end point of the segment
308 double arcStartAngle = lineAngle - M_PI / 2.0;
309 cairo_arc( m_currentContext, center_b.x, center_b.y, radius, arcStartAngle,
310 arcStartAngle + M_PI );
311
312 // Draw the rounded start point of the segment
313 arcStartAngle = lineAngle + M_PI / 2.0;
314 cairo_arc( m_currentContext, center_a.x, center_a.y, radius, arcStartAngle,
315 arcStartAngle + M_PI );
316
317 flushPath();
318 }
319
320 m_isElementAdded = true;
321}
322
323
324void CAIRO_GAL_BASE::DrawHoleWall( const VECTOR2D& aCenterPoint, double aRadius, double aWallWidth )
325{
326 DrawCircle( aCenterPoint, aRadius + aWallWidth );
327}
328
329
330void CAIRO_GAL_BASE::DrawCircle( const VECTOR2D& aCenterPoint, double aRadius )
331{
333
334 VECTOR2D c = roundp( xform( aCenterPoint ) );
335 double r = ::roundp( xform( aRadius ) );
336
337 cairo_set_line_width( m_currentContext, std::min( 2.0 * r, m_lineWidthInPixels ) );
338 cairo_new_sub_path( m_currentContext );
339 cairo_arc( m_currentContext, c.x, c.y, r, 0.0, 2 * M_PI );
340 cairo_close_path( m_currentContext );
341 flushPath();
342 m_isElementAdded = true;
343}
344
345
346void CAIRO_GAL_BASE::DrawArc( const VECTOR2D& aCenterPoint, double aRadius,
347 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle )
348{
350
351 double startAngle = aStartAngle.AsRadians();
352 double endAngle = startAngle + aAngle.AsRadians();
353
354 // calculate start and end arc angles according to the rotation transform matrix
355 // and normalize:
356 arc_angles_xform_and_normalize( startAngle, endAngle );
357
358 double r = xform( aRadius );
359
360 // Adjust center and radius slightly to better match the rounding of endpoints.
361 VECTOR2D mid = roundp( xform( aCenterPoint ) );
362
363 VECTOR2D startPointS = VECTOR2D( r, 0.0 );
364 VECTOR2D endPointS = VECTOR2D( r, 0.0 );
365 RotatePoint( startPointS, -EDA_ANGLE( startAngle, RADIANS_T ) );
366 RotatePoint( endPointS, -EDA_ANGLE( endAngle, RADIANS_T ) );
367
368 VECTOR2D refStart = roundp( xform( aCenterPoint ) + startPointS );
369 VECTOR2D refEnd = roundp( xform( aCenterPoint ) + endPointS );
370
371 r = ( ( refStart - mid ).EuclideanNorm() + ( refEnd - mid ).EuclideanNorm() ) / 2.0;
372
373 cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
374 cairo_new_sub_path( m_currentContext );
375
376 if( m_isFillEnabled )
377 cairo_move_to( m_currentContext, mid.x, mid.y );
378
379 cairo_arc( m_currentContext, mid.x, mid.y, r, startAngle, endAngle );
380
381 if( m_isFillEnabled )
382 cairo_close_path( m_currentContext );
383
384 flushPath();
385
386 m_isElementAdded = true;
387}
388
389
390void CAIRO_GAL_BASE::DrawArcSegment( const VECTOR2D& aCenterPoint, double aRadius,
391 const EDA_ANGLE& aStartAngle, const EDA_ANGLE& aAngle,
392 double aWidth, double aMaxError )
393{
394 // Note: aMaxError is not used because Cairo can draw true arcs
395 if( m_isFillEnabled )
396 {
397 m_lineWidth = aWidth;
398 m_isStrokeEnabled = true;
399 m_isFillEnabled = false;
400 DrawArc( aCenterPoint, aRadius, aStartAngle, aAngle );
401 m_isFillEnabled = true;
402 m_isStrokeEnabled = false;
403 return;
404 }
405
407
408 // calculate start and end arc angles according to the rotation transform matrix
409 // and normalize:
410 double startAngleS = aStartAngle.AsRadians();
411 double endAngleS = startAngleS + aAngle.AsRadians();
412 arc_angles_xform_and_normalize( startAngleS, endAngleS );
413
414 double r = xform( aRadius );
415
416 VECTOR2D mid = xform( aCenterPoint );
417 double width = xform( aWidth / 2.0 );
418 VECTOR2D startPointS = VECTOR2D( r, 0.0 );
419 VECTOR2D endPointS = VECTOR2D( r, 0.0 );
420 RotatePoint( startPointS, -EDA_ANGLE( startAngleS, RADIANS_T ) );
421 RotatePoint( endPointS, -EDA_ANGLE( endAngleS, RADIANS_T ) );
422
423 cairo_save( m_currentContext );
424
425 cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g, m_strokeColor.b,
426 m_strokeColor.a );
427
428 cairo_translate( m_currentContext, mid.x, mid.y );
429
430 cairo_new_sub_path( m_currentContext );
431 cairo_arc( m_currentContext, 0, 0, r - width, startAngleS, endAngleS );
432
433 cairo_new_sub_path( m_currentContext );
434 cairo_arc( m_currentContext, 0, 0, r + width, startAngleS, endAngleS );
435
436 cairo_new_sub_path( m_currentContext );
437 cairo_arc_negative( m_currentContext, startPointS.x, startPointS.y, width, startAngleS,
438 startAngleS + M_PI );
439
440 cairo_new_sub_path( m_currentContext );
441 cairo_arc( m_currentContext, endPointS.x, endPointS.y, width, endAngleS, endAngleS + M_PI );
442
443 cairo_restore( m_currentContext );
444 flushPath();
445
446 m_isElementAdded = true;
447}
448
449
450void CAIRO_GAL_BASE::DrawEllipse( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
451 const EDA_ANGLE& aRotation )
452{
454
455 VECTOR2D c = roundp( xform( aCenterPoint ) );
456 double a = ::roundp( xform( aMajorRadius ) );
457 double b = ::roundp( xform( aMinorRadius ) );
458 double rotation = angle_xform( aRotation.AsRadians() );
459
460 cairo_set_line_width( m_currentContext, std::min( 2.0 * std::min( a, b ), m_lineWidthInPixels ) );
461
462 cairo_save( m_currentContext );
463
464 cairo_translate( m_currentContext, c.x, c.y );
465 cairo_rotate( m_currentContext, rotation );
466 cairo_scale( m_currentContext, a, b );
467
468 cairo_new_sub_path( m_currentContext );
469 cairo_arc( m_currentContext, 0.0, 0.0, 1.0, 0.0, 2.0 * M_PI );
470 cairo_close_path( m_currentContext );
471
472 cairo_restore( m_currentContext );
473
474 flushPath();
475
476 m_isElementAdded = true;
477}
478
479
480void CAIRO_GAL_BASE::DrawEllipseArc( const VECTOR2D& aCenterPoint, double aMajorRadius, double aMinorRadius,
481 const EDA_ANGLE& aRotation, const EDA_ANGLE& aStartAngle,
482 const EDA_ANGLE& aEndAngle )
483{
485
486 VECTOR2D c = roundp( xform( aCenterPoint ) );
487 double a = ::roundp( xform( aMajorRadius ) );
488 double b = ::roundp( xform( aMinorRadius ) );
489 double rotation = angle_xform( aRotation.AsRadians() );
490
491 double startAngle = aStartAngle.AsRadians();
492 double endAngle = aEndAngle.AsRadians();
493 arc_angles_xform_and_normalize( startAngle, endAngle );
494
495 cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
496
497 cairo_save( m_currentContext );
498
499 cairo_translate( m_currentContext, c.x, c.y );
500 cairo_rotate( m_currentContext, rotation );
501 cairo_scale( m_currentContext, a, b );
502
503 cairo_new_sub_path( m_currentContext );
504
505 // For filled arcs, draw as a pie slice
506 if( m_isFillEnabled )
507 cairo_move_to( m_currentContext, 0.0, 0.0 );
508
509 cairo_arc( m_currentContext, 0.0, 0.0, 1.0, startAngle, endAngle );
510
511 if( m_isFillEnabled )
512 cairo_close_path( m_currentContext );
513
514 cairo_restore( m_currentContext );
515
516 flushPath();
517
518 m_isElementAdded = true;
519}
520
521
522void CAIRO_GAL_BASE::DrawRectangle( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
523{
524 // Calculate the diagonal points
526
527 const VECTOR2D p0 = roundp( xform( aStartPoint ) );
528 const VECTOR2D p1 = roundp( xform( VECTOR2D( aEndPoint.x, aStartPoint.y ) ) );
529 const VECTOR2D p2 = roundp( xform( aEndPoint ) );
530 const VECTOR2D p3 = roundp( xform( VECTOR2D( aStartPoint.x, aEndPoint.y ) ) );
531
532 // The path is composed from 4 segments
533 cairo_move_to( m_currentContext, p0.x, p0.y );
534 cairo_line_to( m_currentContext, p1.x, p1.y );
535 cairo_line_to( m_currentContext, p2.x, p2.y );
536 cairo_line_to( m_currentContext, p3.x, p3.y );
537 cairo_close_path( m_currentContext );
538 flushPath();
539
540 m_isElementAdded = true;
541}
542
543
544void CAIRO_GAL_BASE::DrawPolygon( const SHAPE_POLY_SET& aPolySet, bool aStrokeTriangulation )
545{
546 for( int i = 0; i < aPolySet.OutlineCount(); ++i )
547 drawPoly( aPolySet.COutline( i ) );
548}
549
550
552{
553 drawPoly( aPolygon );
554}
555
556
557void CAIRO_GAL_BASE::DrawCurve( const VECTOR2D& aStartPoint, const VECTOR2D& aControlPointA,
558 const VECTOR2D& aControlPointB, const VECTOR2D& aEndPoint,
559 double aFilterValue )
560{
561 // Note: aFilterValue is not used because the cubic Bezier curve is
562 // supported by Cairo.
564
565 const VECTOR2D sp = roundp( xform( aStartPoint ) );
566 const VECTOR2D cpa = roundp( xform( aControlPointA ) );
567 const VECTOR2D cpb = roundp( xform( aControlPointB ) );
568 const VECTOR2D ep = roundp( xform( aEndPoint ) );
569
570 cairo_move_to( m_currentContext, sp.x, sp.y );
571 cairo_curve_to( m_currentContext, cpa.x, cpa.y, cpb.x, cpb.y, ep.x, ep.y );
572 cairo_line_to( m_currentContext, ep.x, ep.y );
573
574 flushPath();
575 m_isElementAdded = true;
576}
577
578
579void CAIRO_GAL_BASE::DrawBitmap( const BITMAP_BASE& aBitmap, double alphaBlend )
580{
581 cairo_save( m_currentContext );
582
583 alphaBlend = std::clamp( alphaBlend, 0.0, 1.0 );
584
585 // We have to calculate the pixel size in users units to draw the image.
586 // m_worldUnitLength is a factor used for converting IU to inches
587 double scale = 1.0 / ( aBitmap.GetPPI() * m_worldUnitLength );
588
589 // The position of the bitmap is the bitmap center.
590 // move the draw origin to the top left bitmap corner:
591 int w = aBitmap.GetSizePixels().x;
592 int h = aBitmap.GetSizePixels().y;
593
594 cairo_set_matrix( m_currentContext, &m_currentWorld2Screen );
595 cairo_scale( m_currentContext, scale, scale );
596 cairo_translate( m_currentContext, -w / 2.0, -h / 2.0 );
597
598 cairo_new_path( m_currentContext );
599 cairo_surface_t* image = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, w, h );
600 cairo_surface_flush( image );
601
602 unsigned char* pix_buffer = cairo_image_surface_get_data( image );
603
604 // The pixel buffer of the initial bitmap:
605 const wxImage& bm_pix_buffer = *aBitmap.GetImageData();
606
607 uint32_t mask_color = ( bm_pix_buffer.GetMaskRed() << 16 )
608 + ( bm_pix_buffer.GetMaskGreen() << 8 ) + ( bm_pix_buffer.GetMaskBlue() );
609
610 // Copy the source bitmap to the cairo bitmap buffer.
611 // In cairo bitmap buffer, a ARGB32 bitmap is an ARGB pixel packed into a uint_32
612 // 24 low bits only are used for color, top 8 are transparency.
613 for( int row = 0; row < h; row++ )
614 {
615 for( int col = 0; col < w; col++ )
616 {
617 unsigned char r = bm_pix_buffer.GetRed( col, row );
618 unsigned char g = bm_pix_buffer.GetGreen( col, row );
619 unsigned char b = bm_pix_buffer.GetBlue( col, row );
620 unsigned char a = wxALPHA_OPAQUE;
621
622 if( bm_pix_buffer.HasAlpha() )
623 {
624 a = bm_pix_buffer.GetAlpha( col, row );
625
626 // ARGB32 format needs pre-multiplied alpha
627 r = uint32_t( r ) * a / 0xFF;
628 g = uint32_t( g ) * a / 0xFF;
629 b = uint32_t( b ) * a / 0xFF;
630 }
631 else if( bm_pix_buffer.HasMask() && (uint32_t)( r << 16 | g << 8 | b ) == mask_color )
632 {
633 a = wxALPHA_TRANSPARENT;
634 }
635
636 // Build the ARGB24 pixel:
637 uint32_t pixel = a << 24 | r << 16 | g << 8 | b;
638
639 // Write the pixel to the cairo image buffer:
640 uint32_t* pix_ptr = (uint32_t*) pix_buffer;
641 *pix_ptr = pixel;
642 pix_buffer += 4;
643 }
644 }
645
646 cairo_surface_mark_dirty( image );
647 cairo_set_source_surface( m_currentContext, image, 0, 0 );
648 cairo_paint_with_alpha( m_currentContext, alphaBlend );
649
650 // store the image handle so it can be destroyed later
651 m_imageSurfaces.push_back( image );
652
653 m_isElementAdded = true;
654
655 cairo_restore( m_currentContext );
656}
657
658
659void CAIRO_GAL_BASE::ResizeScreen( int aWidth, int aHeight )
660{
661 m_screenSize = VECTOR2I( aWidth, aHeight );
662}
663
664
666{
667 storePath();
668}
669
670
672{
673 cairo_operator_t oldOp = cairo_get_operator( m_currentContext );
674 cairo_set_source_rgba( m_currentContext, m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.a );
675 cairo_set_operator( m_currentContext, CAIRO_OPERATOR_SOURCE );
676 cairo_rectangle( m_currentContext, 0.0, 0.0, m_screenSize.x, m_screenSize.y );
677 cairo_fill( m_currentContext );
678 cairo_set_operator( m_currentContext, oldOp );
679}
680
681
682void CAIRO_GAL_BASE::SetIsFill( bool aIsFillEnabled )
683{
684 storePath();
685 m_isFillEnabled = aIsFillEnabled;
686
687 if( m_isGrouping )
688 {
689 GROUP_ELEMENT groupElement;
690 groupElement.m_Command = CMD_SET_FILL;
691 groupElement.m_Argument.BoolArg = aIsFillEnabled;
692 m_currentGroup->push_back( groupElement );
693 }
694}
695
696
697void CAIRO_GAL_BASE::SetIsStroke( bool aIsStrokeEnabled )
698{
699 storePath();
700 m_isStrokeEnabled = aIsStrokeEnabled;
701
702 if( m_isGrouping )
703 {
704 GROUP_ELEMENT groupElement;
705 groupElement.m_Command = CMD_SET_STROKE;
706 groupElement.m_Argument.BoolArg = aIsStrokeEnabled;
707 m_currentGroup->push_back( groupElement );
708 }
709}
710
711
713{
714 storePath();
715 m_strokeColor = aColor;
716
717 if( m_isGrouping )
718 {
719 GROUP_ELEMENT groupElement;
720 groupElement.m_Command = CMD_SET_STROKECOLOR;
721 groupElement.m_Argument.DblArg[0] = m_strokeColor.r;
722 groupElement.m_Argument.DblArg[1] = m_strokeColor.g;
723 groupElement.m_Argument.DblArg[2] = m_strokeColor.b;
724 groupElement.m_Argument.DblArg[3] = m_strokeColor.a;
725 m_currentGroup->push_back( groupElement );
726 }
727}
728
729
731{
732 storePath();
733 m_fillColor = aColor;
734
735 if( m_isGrouping )
736 {
737 GROUP_ELEMENT groupElement;
738 groupElement.m_Command = CMD_SET_FILLCOLOR;
739 groupElement.m_Argument.DblArg[0] = m_fillColor.r;
740 groupElement.m_Argument.DblArg[1] = m_fillColor.g;
741 groupElement.m_Argument.DblArg[2] = m_fillColor.b;
742 groupElement.m_Argument.DblArg[3] = m_fillColor.a;
743 m_currentGroup->push_back( groupElement );
744 }
745}
746
747
748void CAIRO_GAL_BASE::SetLineWidth( float aLineWidth )
749{
750 storePath();
751 GAL::SetLineWidth( aLineWidth );
752
753 if( m_isGrouping )
754 {
755 GROUP_ELEMENT groupElement;
756 groupElement.m_Command = CMD_SET_LINE_WIDTH;
757 groupElement.m_Argument.DblArg[0] = aLineWidth;
758 m_currentGroup->push_back( groupElement );
759 }
760 else
761 {
762 m_lineWidth = aLineWidth;
763 }
764}
765
766
767void CAIRO_GAL_BASE::SetLayerDepth( double aLayerDepth )
768{
769 super::SetLayerDepth( aLayerDepth );
770 storePath();
771}
772
773
774void CAIRO_GAL_BASE::Transform( const MATRIX3x3D& aTransformation )
775{
776 cairo_matrix_t cairoTransformation, newXform;
777
778 cairo_matrix_init( &cairoTransformation, aTransformation.m_data[0][0],
779 aTransformation.m_data[1][0], aTransformation.m_data[0][1],
780 aTransformation.m_data[1][1], aTransformation.m_data[0][2],
781 aTransformation.m_data[1][2] );
782
783 cairo_matrix_multiply( &newXform, &m_currentXform, &cairoTransformation );
784 m_currentXform = newXform;
786}
787
788
789void CAIRO_GAL_BASE::Rotate( double aAngle )
790{
791 storePath();
792
793 if( m_isGrouping )
794 {
795 GROUP_ELEMENT groupElement;
796 groupElement.m_Command = CMD_ROTATE;
797 groupElement.m_Argument.DblArg[0] = aAngle;
798 m_currentGroup->push_back( groupElement );
799 }
800 else
801 {
802 cairo_matrix_rotate( &m_currentXform, aAngle );
804 }
805}
806
807
808void CAIRO_GAL_BASE::Translate( const VECTOR2D& aTranslation )
809{
810 storePath();
811
812 if( m_isGrouping )
813 {
814 GROUP_ELEMENT groupElement;
815 groupElement.m_Command = CMD_TRANSLATE;
816 groupElement.m_Argument.DblArg[0] = aTranslation.x;
817 groupElement.m_Argument.DblArg[1] = aTranslation.y;
818 m_currentGroup->push_back( groupElement );
819 }
820 else
821 {
822 cairo_matrix_translate( &m_currentXform, aTranslation.x, aTranslation.y );
824 }
825}
826
827
828void CAIRO_GAL_BASE::Scale( const VECTOR2D& aScale )
829{
830 storePath();
831
832 if( m_isGrouping )
833 {
834 GROUP_ELEMENT groupElement;
835 groupElement.m_Command = CMD_SCALE;
836 groupElement.m_Argument.DblArg[0] = aScale.x;
837 groupElement.m_Argument.DblArg[1] = aScale.y;
838 m_currentGroup->push_back( groupElement );
839 }
840 else
841 {
842 cairo_matrix_scale( &m_currentXform, aScale.x, aScale.y );
844 }
845}
846
847
849{
850 storePath();
851
852 if( m_isGrouping )
853 {
854 GROUP_ELEMENT groupElement;
855 groupElement.m_Command = CMD_SAVE;
856 m_currentGroup->push_back( groupElement );
857 }
858 else
859 {
860 m_xformStack.push_back( m_currentXform );
862 }
863}
864
865
867{
868 storePath();
869
870 if( m_isGrouping )
871 {
872 GROUP_ELEMENT groupElement;
873 groupElement.m_Command = CMD_RESTORE;
874 m_currentGroup->push_back( groupElement );
875 }
876 else
877 {
878 if( !m_xformStack.empty() )
879 {
881 m_xformStack.pop_back();
883 }
884 }
885}
886
887
889{
890 // If the grouping is started: the actual path is stored in the group, when
891 // a attribute was changed or when grouping stops with the end group method.
892 storePath();
893
894 GROUP group;
895 int groupNumber = getNewGroupNumber();
896 m_groups.insert( std::make_pair( groupNumber, group ) );
897 m_currentGroup = &m_groups[groupNumber];
898 m_isGrouping = true;
899
900 return groupNumber;
901}
902
903
905{
906 storePath();
907 m_isGrouping = false;
908}
909
910
911void CAIRO_GAL_BASE::DrawGroup( int aGroupNumber )
912{
913 // This method implements a small Virtual Machine - all stored commands
914 // are executed; nested calling is also possible
915
916 storePath();
917
918 for( auto it = m_groups[aGroupNumber].begin(); it != m_groups[aGroupNumber].end(); ++it )
919 {
920 switch( it->m_Command )
921 {
922 case CMD_SET_FILL:
923 m_isFillEnabled = it->m_Argument.BoolArg;
924 break;
925
926 case CMD_SET_STROKE:
927 m_isStrokeEnabled = it->m_Argument.BoolArg;
928 break;
929
931 m_fillColor = COLOR4D( it->m_Argument.DblArg[0], it->m_Argument.DblArg[1],
932 it->m_Argument.DblArg[2], it->m_Argument.DblArg[3] );
933 break;
934
936 m_strokeColor = COLOR4D( it->m_Argument.DblArg[0], it->m_Argument.DblArg[1],
937 it->m_Argument.DblArg[2], it->m_Argument.DblArg[3] );
938 break;
939
941 {
942 // Make lines appear at least 1 pixel wide, no matter of zoom
943 double x = 1.0, y = 1.0;
944 cairo_device_to_user_distance( m_currentContext, &x, &y );
945 double minWidth = std::min( fabs( x ), fabs( y ) );
946 cairo_set_line_width( m_currentContext,
947 std::max( it->m_Argument.DblArg[0], minWidth ) );
948 break;
949 }
950
951
952 case CMD_STROKE_PATH:
953 cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g,
955 cairo_append_path( m_currentContext, it->m_CairoPath );
956 cairo_stroke( m_currentContext );
957 break;
958
959 case CMD_FILL_PATH:
960 cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g, m_fillColor.b,
961 m_strokeColor.a );
962 cairo_append_path( m_currentContext, it->m_CairoPath );
963 cairo_fill( m_currentContext );
964 break;
965
966 /*
967 case CMD_TRANSFORM:
968 cairo_matrix_t matrix;
969 cairo_matrix_init( &matrix, it->argument.DblArg[0], it->argument.DblArg[1],
970 it->argument.DblArg[2], it->argument.DblArg[3],
971 it->argument.DblArg[4], it->argument.DblArg[5] );
972 cairo_transform( m_currentContext, &matrix );
973 break;
974 */
975
976 case CMD_ROTATE:
977 cairo_rotate( m_currentContext, it->m_Argument.DblArg[0] );
978 break;
979
980 case CMD_TRANSLATE:
981 cairo_translate( m_currentContext, it->m_Argument.DblArg[0], it->m_Argument.DblArg[1] );
982 break;
983
984 case CMD_SCALE:
985 cairo_scale( m_currentContext, it->m_Argument.DblArg[0], it->m_Argument.DblArg[1] );
986 break;
987
988 case CMD_SAVE:
989 cairo_save( m_currentContext );
990 break;
991
992 case CMD_RESTORE:
993 cairo_restore( m_currentContext );
994 break;
995
996 case CMD_CALL_GROUP:
997 DrawGroup( it->m_Argument.IntArg );
998 break;
999 }
1000 }
1001}
1002
1003
1004void CAIRO_GAL_BASE::ChangeGroupColor( int aGroupNumber, const COLOR4D& aNewColor )
1005{
1006 storePath();
1007
1008 for( auto it = m_groups[aGroupNumber].begin(); it != m_groups[aGroupNumber].end(); ++it )
1009 {
1010 if( it->m_Command == CMD_SET_FILLCOLOR || it->m_Command == CMD_SET_STROKECOLOR )
1011 {
1012 it->m_Argument.DblArg[0] = aNewColor.r;
1013 it->m_Argument.DblArg[1] = aNewColor.g;
1014 it->m_Argument.DblArg[2] = aNewColor.b;
1015 it->m_Argument.DblArg[3] = aNewColor.a;
1016 }
1017 }
1018}
1019
1020
1021void CAIRO_GAL_BASE::ChangeGroupDepth( int aGroupNumber, int aDepth )
1022{
1023 // Cairo does not have any possibilities to change the depth coordinate of stored items,
1024 // it depends only on the order of drawing
1025}
1026
1027
1028void CAIRO_GAL_BASE::DeleteGroup( int aGroupNumber )
1029{
1030 storePath();
1031
1032 // Delete the Cairo paths
1033 std::deque<GROUP_ELEMENT>::iterator it, end;
1034
1035 for( it = m_groups[aGroupNumber].begin(), end = m_groups[aGroupNumber].end(); it != end; ++it )
1036 {
1037 if( it->m_Command == CMD_FILL_PATH || it->m_Command == CMD_STROKE_PATH )
1038 cairo_path_destroy( it->m_CairoPath );
1039 }
1040
1041 // Delete the group
1042 m_groups.erase( aGroupNumber );
1043}
1044
1045
1047{
1048 for( auto it = m_groups.begin(); it != m_groups.end(); )
1049 DeleteGroup( ( it++ )->first );
1050}
1051
1052
1054{
1055 cairo_set_operator( m_currentContext, aSetting ? CAIRO_OPERATOR_CLEAR : CAIRO_OPERATOR_OVER );
1056}
1057
1058
1064
1065
1067{
1068 m_compositor->DrawBuffer( m_tempBuffer, m_mainBuffer, CAIRO_OPERATOR_DIFFERENCE );
1069}
1070
1071
1077
1078
1080{
1081 m_compositor->DrawBuffer( m_tempBuffer, m_mainBuffer, CAIRO_OPERATOR_OVER );
1082}
1083
1084
1085void CAIRO_GAL_BASE::DrawCursor( const VECTOR2D& aCursorPosition )
1086{
1087 m_cursorPosition = aCursorPosition;
1088}
1089
1090
1092{
1093}
1094
1095
1097{
1098 for( _cairo_surface* imageSurface : m_imageSurfaces )
1099 cairo_surface_destroy( imageSurface );
1100
1101 m_imageSurfaces.clear();
1102
1103 ClearScreen();
1104
1105 // Compute the world <-> screen transformations
1107
1108 cairo_matrix_init( &m_cairoWorldScreenMatrix, m_worldScreenMatrix.m_data[0][0],
1109 m_worldScreenMatrix.m_data[1][0], m_worldScreenMatrix.m_data[0][1],
1110 m_worldScreenMatrix.m_data[1][1], m_worldScreenMatrix.m_data[0][2],
1111 m_worldScreenMatrix.m_data[1][2] );
1112
1113 // we work in screen-space coordinates and do the transforms outside.
1114 cairo_identity_matrix( m_context );
1115
1116 cairo_matrix_init_identity( &m_currentXform );
1117
1118 // Start drawing with a new path
1119 cairo_new_path( m_context );
1120 m_isElementAdded = true;
1121
1123
1124 m_lineWidth = 0;
1125}
1126
1127
1128void CAIRO_GAL_BASE::drawAxes( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint )
1129{
1130 syncLineWidth();
1131
1132 VECTOR2D p0 = roundp( xform( aStartPoint ) );
1133 VECTOR2D p1 = roundp( xform( aEndPoint ) );
1134 VECTOR2D org = roundp( xform( VECTOR2D( 0.0, 0.0 ) ) ); // Axis origin = 0,0 coord
1135
1136 cairo_set_source_rgba( m_currentContext, m_axesColor.r, m_axesColor.g, m_axesColor.b,
1137 m_axesColor.a );
1138 cairo_move_to( m_currentContext, p0.x, org.y );
1139 cairo_line_to( m_currentContext, p1.x, org.y );
1140 cairo_move_to( m_currentContext, org.x, p0.y );
1141 cairo_line_to( m_currentContext, org.x, p1.y );
1142 cairo_stroke( m_currentContext );
1143}
1144
1145
1146void CAIRO_GAL_BASE::drawGridLine( const VECTOR2D& aStartPoint, const VECTOR2D& aEndPoint,
1147 const COLOR4D& aColor )
1148{
1149 syncLineWidth();
1150 VECTOR2D p0 = roundp( xform( aStartPoint ) );
1151 VECTOR2D p1 = roundp( xform( aEndPoint ) );
1152
1153 cairo_set_source_rgba( m_currentContext, aColor.r, aColor.g, aColor.b, aColor.a );
1154 cairo_move_to( m_currentContext, p0.x, p0.y );
1155 cairo_line_to( m_currentContext, p1.x, p1.y );
1156 cairo_stroke( m_currentContext );
1157}
1158
1159
1160void CAIRO_GAL_BASE::drawGridCross( const VECTOR2D& aPoint, const COLOR4D& aColor )
1161{
1162 syncLineWidth();
1163 VECTOR2D offset( 0, 0 );
1164 double size = 2.0 * m_lineWidthInPixels + 0.5;
1165
1166 VECTOR2D p0 = roundp( xform( aPoint ) ) - VECTOR2D( size, 0 ) + offset;
1167 VECTOR2D p1 = roundp( xform( aPoint ) ) + VECTOR2D( size, 0 ) + offset;
1168 VECTOR2D p2 = roundp( xform( aPoint ) ) - VECTOR2D( 0, size ) + offset;
1169 VECTOR2D p3 = roundp( xform( aPoint ) ) + VECTOR2D( 0, size ) + offset;
1170
1171 cairo_set_source_rgba( m_currentContext, aColor.r, aColor.g, aColor.b, aColor.a );
1172 cairo_move_to( m_currentContext, p0.x, p0.y );
1173 cairo_line_to( m_currentContext, p1.x, p1.y );
1174 cairo_move_to( m_currentContext, p2.x, p2.y );
1175 cairo_line_to( m_currentContext, p3.x, p3.y );
1176 cairo_stroke( m_currentContext );
1177}
1178
1179
1180void CAIRO_GAL_BASE::drawGridPoint( const VECTOR2D& aPoint, double aWidth, double aHeight,
1181 const COLOR4D& aColor )
1182{
1183 VECTOR2D p = roundp( xform( aPoint ) );
1184
1185 double sw = std::max( 1.0, aWidth );
1186 double sh = std::max( 1.0, aHeight );
1187
1188 cairo_set_source_rgba( m_currentContext, aColor.r, aColor.g, aColor.b, aColor.a );
1189 cairo_rectangle( m_currentContext, p.x - std::floor( sw / 2 ) - 0.5,
1190 p.y - std::floor( sh / 2 ) - 0.5, sw, sh );
1191
1192 cairo_fill( m_currentContext );
1193}
1194
1195
1197{
1198 if( m_isFillEnabled )
1199 {
1200 cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g, m_fillColor.b,
1201 m_fillColor.a );
1202
1203 if( m_isStrokeEnabled )
1204 {
1205 cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
1206 cairo_fill_preserve( m_currentContext );
1207 }
1208 else
1209 {
1210 cairo_fill( m_currentContext );
1211 }
1212 }
1213
1214 if( m_isStrokeEnabled )
1215 {
1216 cairo_set_line_width( m_currentContext, m_lineWidthInPixels );
1217 cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g, m_strokeColor.b,
1218 m_strokeColor.a );
1219 cairo_stroke( m_currentContext );
1220 }
1221}
1222
1223
1225{
1226 if( m_isElementAdded )
1227 {
1228 m_isElementAdded = false;
1229
1230 if( !m_isGrouping )
1231 {
1232 if( m_isFillEnabled )
1233 {
1234 cairo_set_source_rgba( m_currentContext, m_fillColor.r, m_fillColor.g,
1235 m_fillColor.b, m_fillColor.a );
1236 cairo_fill_preserve( m_currentContext );
1237 }
1238
1239 if( m_isStrokeEnabled )
1240 {
1241 cairo_set_source_rgba( m_currentContext, m_strokeColor.r, m_strokeColor.g,
1243 cairo_stroke_preserve( m_currentContext );
1244 }
1245 }
1246 else
1247 {
1248 // Copy the actual path, append it to the global path list
1249 // then check, if the path needs to be stroked/filled and
1250 // add this command to the group list;
1251 if( m_isStrokeEnabled )
1252 {
1253 GROUP_ELEMENT groupElement;
1254 groupElement.m_CairoPath = cairo_copy_path( m_currentContext );
1255 groupElement.m_Command = CMD_STROKE_PATH;
1256 m_currentGroup->push_back( groupElement );
1257 }
1258
1259 if( m_isFillEnabled )
1260 {
1261 GROUP_ELEMENT groupElement;
1262 groupElement.m_CairoPath = cairo_copy_path( m_currentContext );
1263 groupElement.m_Command = CMD_FILL_PATH;
1264 m_currentGroup->push_back( groupElement );
1265 }
1266 }
1267
1268 cairo_new_path( m_currentContext );
1269 }
1270}
1271
1272
1273void CAIRO_GAL_BASE::blitCursor( wxMemoryDC& clientDC )
1274{
1275 if( !IsCursorEnabled() )
1276 return;
1277
1279 const COLOR4D cColor = getCursorColor();
1280
1281 wxColour color( cColor.r * cColor.a * 255, cColor.g * cColor.a * 255, cColor.b * cColor.a * 255,
1282 255 );
1283 clientDC.SetPen( wxPen( color ) );
1284
1286 {
1287 clientDC.DrawLine( 0, p.y, m_screenSize.x, p.y );
1288 clientDC.DrawLine( p.x, 0, p.x, m_screenSize.y );
1289 }
1291 {
1292 // Oversized but that's ok
1293 int diagonalSize = m_screenSize.x + m_screenSize.y;
1294 clientDC.DrawLine( p.x - diagonalSize, p.y - diagonalSize,
1295 p.x + diagonalSize, p.y + diagonalSize );
1296 clientDC.DrawLine( p.x - diagonalSize, p.y + diagonalSize,
1297 p.x + diagonalSize, p.y - diagonalSize );
1298 }
1299 else
1300 {
1301 const int cursorSize = 80;
1302 clientDC.DrawLine( p.x - cursorSize / 2, p.y, p.x + cursorSize / 2, p.y );
1303 clientDC.DrawLine( p.x, p.y - cursorSize / 2, p.x, p.y + cursorSize / 2 );
1304 }
1305}
1306
1307
1308void CAIRO_GAL_BASE::drawPoly( const std::deque<VECTOR2D>& aPointList )
1309{
1310 if( aPointList.size() <= 1 )
1311 return;
1312
1313 // Iterate over the point list and draw the segments
1314 std::deque<VECTOR2D>::const_iterator it = aPointList.begin();
1315
1316 syncLineWidth();
1317
1318 const VECTOR2D p = roundp( xform( it->x, it->y ) );
1319
1320 cairo_move_to( m_currentContext, p.x, p.y );
1321
1322 for( ++it; it != aPointList.end(); ++it )
1323 {
1324 const VECTOR2D p2 = roundp( xform( it->x, it->y ) );
1325
1326 cairo_line_to( m_currentContext, p2.x, p2.y );
1327 }
1328
1329 flushPath();
1330 m_isElementAdded = true;
1331}
1332
1333
1334void CAIRO_GAL_BASE::drawPoly( const std::vector<VECTOR2D>& aPointList )
1335{
1336 if( aPointList.size() <= 1 )
1337 return;
1338
1339 // Iterate over the point list and draw the segments
1340 std::vector<VECTOR2D>::const_iterator it = aPointList.begin();
1341
1342 syncLineWidth();
1343
1344 const VECTOR2D p = roundp( xform( it->x, it->y ) );
1345
1346 cairo_move_to( m_currentContext, p.x, p.y );
1347
1348 for( ++it; it != aPointList.end(); ++it )
1349 {
1350 const VECTOR2D p2 = roundp( xform( it->x, it->y ) );
1351
1352 cairo_line_to( m_currentContext, p2.x, p2.y );
1353 }
1354
1355 flushPath();
1356 m_isElementAdded = true;
1357}
1358
1359
1360void CAIRO_GAL_BASE::drawPoly( const VECTOR2D aPointList[], int aListSize )
1361{
1362 if( aListSize <= 1 )
1363 return;
1364
1365 // Iterate over the point list and draw the segments
1366 const VECTOR2D* ptr = aPointList;
1367
1368 syncLineWidth();
1369
1370 const VECTOR2D p = roundp( xform( ptr->x, ptr->y ) );
1371 cairo_move_to( m_currentContext, p.x, p.y );
1372
1373 for( int i = 1; i < aListSize; ++i )
1374 {
1375 ++ptr;
1376 const VECTOR2D p2 = roundp( xform( ptr->x, ptr->y ) );
1377 cairo_line_to( m_currentContext, p2.x, p2.y );
1378 }
1379
1380 flushPath();
1381 m_isElementAdded = true;
1382}
1383
1384
1386{
1387 if( aLineChain.PointCount() <= 1 )
1388 return;
1389
1390 syncLineWidth();
1391
1392 auto numPoints = aLineChain.PointCount();
1393
1394 if( aLineChain.IsClosed() )
1395 numPoints += 1;
1396
1397 const VECTOR2I start = aLineChain.CPoint( 0 );
1398 const VECTOR2D p = roundp( xform( start.x, start.y ) );
1399 cairo_move_to( m_currentContext, p.x, p.y );
1400
1401 for( int i = 1; i < numPoints; ++i )
1402 {
1403 const VECTOR2I& pw = aLineChain.CPoint( i );
1404 const VECTOR2D ps = roundp( xform( pw.x, pw.y ) );
1405 cairo_line_to( m_currentContext, ps.x, ps.y );
1406 }
1407
1408 flushPath();
1409 m_isElementAdded = true;
1410}
1411
1412
1414{
1415 wxASSERT_MSG( m_groups.size() < std::numeric_limits<unsigned int>::max(),
1416 wxT( "There are no free slots to store a group" ) );
1417
1418 while( m_groups.find( m_groupCounter ) != m_groups.end() )
1420
1421 return m_groupCounter++;
1422}
1423
1424
1425CAIRO_GAL::CAIRO_GAL( GAL_DISPLAY_OPTIONS& aDisplayOptions, wxWindow* aParent,
1426 wxEvtHandler* aMouseListener, wxEvtHandler* aPaintListener,
1427 const wxString& aName ) :
1428 CAIRO_GAL_BASE( aDisplayOptions ),
1429 wxWindow( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxEXPAND, aName )
1430{
1431 // Initialise compositing state
1432 m_mainBuffer = 0;
1433 m_overlayBuffer = 0;
1434 m_tempBuffer = 0;
1435 m_savedBuffer = 0;
1436 m_validCompositor = false;
1439
1440#ifdef _WIN32
1441 // need to fix broken cairo rendering on Windows with wx 3.3
1442 SetDoubleBuffered( false );
1443#endif
1444
1445 m_bitmapBuffer = nullptr;
1446 m_wxOutput = nullptr;
1447
1448 m_parentWindow = aParent;
1449 m_mouseListener = aMouseListener;
1450 m_paintListener = aPaintListener;
1451
1452 // Connect the native cursor handler
1453 Connect( wxEVT_SET_CURSOR, wxSetCursorEventHandler( CAIRO_GAL::onSetNativeCursor ), nullptr,
1454 this );
1455
1456 // Connecting the event handlers
1457 Connect( wxEVT_PAINT, wxPaintEventHandler( CAIRO_GAL::onPaint ) );
1458
1459 // Mouse events are skipped to the parent
1460 Connect( wxEVT_MOTION, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1461 Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1462 Connect( wxEVT_LEFT_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1463 Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1464 Connect( wxEVT_MIDDLE_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1465 Connect( wxEVT_MIDDLE_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1466 Connect( wxEVT_MIDDLE_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1467 Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1468 Connect( wxEVT_RIGHT_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1469 Connect( wxEVT_RIGHT_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1470 Connect( wxEVT_AUX1_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1471 Connect( wxEVT_AUX1_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1472 Connect( wxEVT_AUX1_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1473 Connect( wxEVT_AUX2_DOWN, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1474 Connect( wxEVT_AUX2_UP, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1475 Connect( wxEVT_AUX2_DCLICK, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1476 Connect( wxEVT_MOUSEWHEEL, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1477
1478#if defined _WIN32 || defined _WIN64
1479 Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( CAIRO_GAL::skipMouseEvent ) );
1480#endif
1481
1482 Bind( wxEVT_GESTURE_ZOOM, &CAIRO_GAL::skipGestureEvent, this );
1483 Bind( wxEVT_GESTURE_PAN, &CAIRO_GAL::skipGestureEvent, this );
1484
1485 SetSize( aParent->GetClientSize() );
1486 m_screenSize = ToVECTOR2I( aParent->GetClientSize() );
1487
1488 // Allocate memory for pixel storage
1490
1491 m_isInitialized = false;
1492}
1493
1494
1499
1500
1502{
1503 initSurface();
1504
1506
1507 if( !m_validCompositor )
1508 setCompositor();
1509
1510 m_compositor->SetMainContext( m_context );
1511 m_compositor->SetBuffer( m_mainBuffer );
1512}
1513
1514
1516{
1518
1519 // Merge buffers on the screen
1520 m_compositor->DrawBuffer( m_mainBuffer );
1521 m_compositor->DrawBuffer( m_overlayBuffer );
1522
1523 // Now translate the raw context data from the format stored
1524 // by cairo into a format understood by wxImage.
1525 int height = m_screenSize.y;
1526 int stride = m_stride;
1527
1528 unsigned char* srcRow = m_bitmapBuffer;
1529 unsigned char* dst = m_wxOutput;
1530
1531 for( int y = 0; y < height; y++ )
1532 {
1533 for( int x = 0; x < stride; x += 4 )
1534 {
1535 const unsigned char* src = srcRow + x;
1536
1537#if defined( __BYTE_ORDER__ ) && ( __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ )
1538 // XRGB
1539 dst[0] = src[1];
1540 dst[1] = src[2];
1541 dst[2] = src[3];
1542#else
1543 // BGRX
1544 dst[0] = src[2];
1545 dst[1] = src[1];
1546 dst[2] = src[0];
1547#endif
1548
1549 dst += 3;
1550 }
1551
1552 srcRow += stride;
1553 }
1554
1555 wxImage img( m_wxBufferWidth, m_screenSize.y, m_wxOutput, true );
1556 wxBitmap bmp( img );
1557 wxMemoryDC mdc( bmp );
1558 wxClientDC clientDC( this );
1559
1560 // This blit runs outside the paint cycle, so it ignores windows stacked over the canvas
1561 // and would erase them on every frame
1562 if( !m_overlayExclusions.empty() )
1563 {
1564 wxPoint origin = GetPosition();
1565 wxRegion visible( 0, 0, clientDC.LogicalToDeviceXRel( m_screenSize.x ),
1566 clientDC.LogicalToDeviceYRel( m_screenSize.y ) );
1567
1568 for( wxRect rect : m_overlayExclusions )
1569 {
1570 rect.Offset( -origin.x, -origin.y );
1571
1572 visible.Subtract( wxRect( clientDC.LogicalToDeviceX( rect.x ),
1573 clientDC.LogicalToDeviceY( rect.y ),
1574 clientDC.LogicalToDeviceXRel( rect.width ),
1575 clientDC.LogicalToDeviceYRel( rect.height ) ) );
1576 }
1577
1578 clientDC.SetDeviceClippingRegion( visible );
1579 }
1580
1581 // Now it is the time to blit the mouse cursor
1582 blitCursor( mdc );
1583 clientDC.Blit( 0, 0, m_screenSize.x, m_screenSize.y, &mdc, 0, 0, wxCOPY );
1584
1585 deinitSurface();
1586}
1587
1588
1589void CAIRO_GAL::PostPaint( wxPaintEvent& aEvent )
1590{
1591 // posts an event to m_paint_listener to ask for redraw the canvas.
1592 if( m_paintListener )
1593 wxPostEvent( m_paintListener, aEvent );
1594}
1595
1596
1597void CAIRO_GAL::ResizeScreen( int aWidth, int aHeight )
1598{
1599 CAIRO_GAL_BASE::ResizeScreen( aWidth, aHeight );
1600
1601 // Recreate the bitmaps
1602 deleteBitmaps();
1604
1605 if( m_validCompositor )
1606 m_compositor->Resize( aWidth, aHeight );
1607
1608 m_validCompositor = false;
1609
1610 SetSize( wxSize( aWidth, aHeight ) );
1611}
1612
1613
1614bool CAIRO_GAL::Show( bool aShow )
1615{
1616 bool s = wxWindow::Show( aShow );
1617
1618 if( aShow )
1619 wxWindow::Raise();
1620
1621 return s;
1622}
1623
1624
1626{
1627 initSurface();
1629}
1630
1631
1637
1638
1640{
1641 // If the compositor is not set, that means that there is a recaching process going on
1642 // and we do not need the compositor now
1643 if( !m_validCompositor )
1644 return;
1645
1646 // Cairo grouping prevents display of overlapping items on the same layer in the lighter color
1647 if( m_isInitialized )
1648 storePath();
1649
1650 switch( aTarget )
1651 {
1652 default:
1653 case TARGET_CACHED:
1654 case TARGET_NONCACHED: m_compositor->SetBuffer( m_mainBuffer ); break;
1655 case TARGET_OVERLAY: m_compositor->SetBuffer( m_overlayBuffer ); break;
1656 case TARGET_TEMP: m_compositor->SetBuffer( m_tempBuffer ); break;
1657 }
1658
1659 m_currentTarget = aTarget;
1660}
1661
1662
1664{
1665 return m_currentTarget;
1666}
1667
1668
1670{
1671 // Save the current state
1672 unsigned int currentBuffer = m_compositor->GetBuffer();
1673
1674 switch( aTarget )
1675 {
1676 // Cached and noncached items are rendered to the same buffer
1677 default:
1678 case TARGET_CACHED:
1679 case TARGET_NONCACHED: m_compositor->SetBuffer( m_mainBuffer ); break;
1680 case TARGET_OVERLAY: m_compositor->SetBuffer( m_overlayBuffer ); break;
1681 case TARGET_TEMP: m_compositor->SetBuffer( m_tempBuffer ); break;
1682 }
1683
1684 m_compositor->ClearBuffer( COLOR4D::BLACK );
1685
1686 // Restore the previous state
1687 m_compositor->SetBuffer( currentBuffer );
1688}
1689
1690
1692{
1693 if( m_isInitialized )
1694 return;
1695
1696 m_surface = cairo_image_surface_create_for_data( m_bitmapBuffer, GAL_FORMAT, m_wxBufferWidth,
1698
1699 m_context = cairo_create( m_surface );
1700
1701#ifdef DEBUG
1702 cairo_status_t status = cairo_status( m_context );
1703 wxASSERT_MSG( status == CAIRO_STATUS_SUCCESS, wxT( "Cairo context creation error" ) );
1704#endif /* DEBUG */
1705
1707
1708 m_isInitialized = true;
1709}
1710
1711
1713{
1714 if( !m_isInitialized )
1715 return;
1716
1717 cairo_destroy( m_context );
1718 m_context = nullptr;
1719 cairo_surface_destroy( m_surface );
1720 m_surface = nullptr;
1721
1722 m_isInitialized = false;
1723}
1724
1725
1727{
1729
1730 // Create buffer, use the system independent Cairo context backend
1731 m_stride = cairo_format_stride_for_width( GAL_FORMAT, m_wxBufferWidth );
1733
1734 wxASSERT( m_bitmapBuffer == nullptr );
1735 m_bitmapBuffer = new unsigned char[m_bufferSize];
1736
1737 wxASSERT( m_wxOutput == nullptr );
1738 m_wxOutput = new unsigned char[m_wxBufferWidth * 3 * m_screenSize.y];
1739}
1740
1741
1743{
1744 delete[] m_bitmapBuffer;
1745 m_bitmapBuffer = nullptr;
1746
1747 delete[] m_wxOutput;
1748 m_wxOutput = nullptr;
1749}
1750
1751
1753{
1754 // Recreate the compositor with the new Cairo context
1756 m_compositor->Resize( m_screenSize.x, m_screenSize.y );
1757 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
1758
1759 // Prepare buffers
1760 m_mainBuffer = m_compositor->CreateBuffer();
1761 m_overlayBuffer = m_compositor->CreateBuffer();
1762 m_tempBuffer = m_compositor->CreateBuffer();
1763
1764 m_validCompositor = true;
1765}
1766
1767
1768void CAIRO_GAL::onPaint( wxPaintEvent& aEvent )
1769{
1770 // A wxPaintDC must be created in wxEVT_PAINT handlers. Without this, the system keeps
1771 // sending paint events because it thinks the window still needs to be painted, causing
1772 // high CPU usage in fallback mode (Cairo).
1773 wxPaintDC dc( this );
1774 PostPaint( aEvent );
1775}
1776
1777
1778void CAIRO_GAL::skipMouseEvent( wxMouseEvent& aEvent )
1779{
1780 // Post the mouse event to the event listener registered in constructor, if any
1781 if( m_mouseListener )
1782 wxPostEvent( m_mouseListener, aEvent );
1783}
1784
1785
1786void CAIRO_GAL::skipGestureEvent( wxGestureEvent& aEvent )
1787{
1788 // Post the gesture event to the event listener registered in constructor, if any
1789 if( m_mouseListener )
1790 wxPostEvent( m_mouseListener, aEvent );
1791}
1792
1793
1795{
1796 bool refresh = false;
1797
1798 if( m_validCompositor &&
1799 aOptions.antialiasing_mode != m_compositor->GetAntialiasingMode() )
1800 {
1801 m_compositor->SetAntialiasingMode( m_options.antialiasing_mode );
1802 m_validCompositor = false;
1803 deinitSurface();
1804
1805 refresh = true;
1806 }
1807
1808 if( super::updatedGalDisplayOptions( aOptions ) )
1809 {
1810 Refresh();
1811 refresh = true;
1812 }
1813
1814 return refresh;
1815}
1816
1817
1818bool CAIRO_GAL::SetNativeCursorStyle( KICURSOR aCursor, bool aHiDPI )
1819{
1820 // Store the current cursor type and get the wx cursor for it
1821 if( !GAL::SetNativeCursorStyle( aCursor, aHiDPI ) )
1822 return false;
1823
1825
1826#if wxCHECK_VERSION( 3, 3, 0 )
1827 wxWindow::SetCursorBundle( m_currentwxCursor );
1828#else
1829 wxWindow::SetCursor( m_currentwxCursor );
1830#endif
1831
1832 return true;
1833}
1834
1835
1836void CAIRO_GAL::onSetNativeCursor( wxSetCursorEvent& aEvent )
1837{
1838#if wxCHECK_VERSION( 3, 3, 0 )
1839 aEvent.SetCursor( m_currentwxCursor.GetCursorFor( this ) );
1840#else
1841 aEvent.SetCursor( m_currentwxCursor );
1842#endif
1843}
1844
1845
1847{
1849
1850 float marker = std::fmax( 1.0f, m_gridLineWidth ) / m_worldScale;
1851
1852 if( m_axesEnabled )
1853 {
1854 VECTOR2D worldStartPoint = m_screenWorldMatrix * VECTOR2D( 0.0, 0.0 );
1856
1857 SetLineWidth( marker );
1858 drawAxes( worldStartPoint, worldEndPoint );
1859 }
1860
1861 const bool renderGlobalGrid = m_gridVisibility && m_gridSize.x != 0 && m_gridSize.y != 0;
1862
1863 if( renderGlobalGrid )
1864 {
1865 GRID_SOURCE globalGrid;
1866 globalGrid.unbounded = true;
1867 globalGrid.axesEnabled = m_axesEnabled;
1869 globalGrid.origin = m_gridOrigin;
1870 globalGrid.pitch = GetVisibleGridSize();
1871 globalGrid.tick = static_cast<unsigned>( m_gridTick );
1872 globalGrid.style = m_gridStyle;
1873 globalGrid.color = m_gridColor;
1874 globalGrid.priority = 0;
1875
1876 m_gridSources.push_back( globalGrid );
1877 }
1878
1879 if( !m_gridSources.empty() )
1881
1882 if( renderGlobalGrid )
1883 m_gridSources.pop_back();
1884}
1885
1886
1888{
1889 if( m_gridSources.empty() )
1890 return;
1891
1892 // Draw the grid behind all other layers
1893 SetLayerDepth( m_depthRange.y * 0.75 );
1894
1895
1896 for(auto & m_gridSource : std::ranges::reverse_view(m_gridSources))
1897 {
1898 if( !m_gridSource.highlighted )
1899 drawGridSource( m_gridSource );
1900 }
1901
1902 for(auto & m_gridSource : std::ranges::reverse_view(m_gridSources))
1903 {
1904 if( m_gridSource.highlighted )
1905 drawGridSource( m_gridSource );
1906 }
1907}
1908
1909
1911{
1912 const COLOR4D color = gridSourceColor( aSrc );
1913 const GRID_LATTICE lattice = gridLattice( aSrc );
1914 COLOR4D wash = m_clearColor;
1915
1916 if( aSrc.highlighted )
1917 wash.a = GRID_DIM_ALPHA;
1918
1919 // "Clear" coverage area by filling it with either m_clearColor or the
1920 // dimming layer
1921 if( !aSrc.unbounded )
1922 {
1923 SetIsFill( true );
1924 SetIsStroke( false );
1925 SetFillColor( wash );
1926 drawGridCoverageShape( aSrc );
1927 }
1928
1929 const float marker = (float)(std::fmax( 1.0f, m_gridLineWidth ) / m_worldScale);
1930 const float doubleMarker = 2.0f * marker;
1931
1932 auto drawCrossAt = [&]( const VECTOR2D& aPos, bool aMajor, double aArmAngle )
1933 {
1934 const float w = aMajor ? doubleMarker : marker;
1935 const double len = 2.0 * w;
1936 const VECTOR2D arm1( std::cos( aArmAngle ) * len, std::sin( aArmAngle ) * len );
1937 const VECTOR2D arm2( -arm1.y, arm1.x );
1938
1939 SetLineWidth( w );
1940 drawGridLine( aPos - arm1, aPos + arm1, color );
1941 drawGridLine( aPos - arm2, aPos + arm2, color );
1942 };
1943
1944 auto drawDotAt = [&]( const VECTOR2D& aPos, bool aMajorX, bool aMajorY )
1945 {
1946 // drawGridPoint doesn't syncLineWidth, so pin the half-pixel rounding itself.
1947 m_lineWidthIsOdd = true;
1948
1949 drawGridPoint( aPos, aMajorX ? m_gridLineWidth * 2.0 : m_gridLineWidth,
1950 aMajorY ? m_gridLineWidth * 2.0 : m_gridLineWidth, color );
1951 };
1952
1953 Save();
1954 Translate( aSrc.origin );
1955 // GAL Rotate is math-convention; grid orientation is screen-convention.
1956 Rotate( -aSrc.orientation );
1957
1958 SetIsFill( false );
1959 SetIsStroke( true );
1960 SetStrokeColor( color );
1961
1962 const double dx = lattice.pitch.x;
1963 const double dy = lattice.pitch.y;
1964
1965 if( aSrc.kind == GRID_SOURCE::KIND::POLAR && lattice.extent.x > 0.0 && lattice.extent.y > 0.0 )
1966 {
1967 const double rMax = lattice.extent.x;
1968 const double phiMax = lattice.extent.y;
1969
1970 if( aSrc.style == GRID_STYLE::LINES )
1971 {
1972 int rIdx = 1;
1973
1974 for( double r = dx; r <= rMax + 1e-6; r += dx, ++rIdx )
1975 {
1976 SetLineWidth( lattice.IsMajor( rIdx ) ? doubleMarker : marker );
1977 DrawArc( VECTOR2D( 0, 0 ), r, EDA_ANGLE( 0, RADIANS_T ),
1978 EDA_ANGLE( phiMax, RADIANS_T ) );
1979 }
1980
1981 int pIdx = 0;
1982
1983 for( double phi = 0; phi <= phiMax + 1e-6; phi += dy, ++pIdx )
1984 {
1985 SetLineWidth( lattice.IsMajor( pIdx ) ? doubleMarker : marker );
1986 drawGridLine( VECTOR2D( 0, 0 ),
1987 VECTOR2D( rMax * std::cos( phi ), rMax * std::sin( phi ) ), color );
1988 }
1989 }
1990 else
1991 {
1992 int rIdx = 0;
1993
1994 for( double r = 0; r <= rMax + 1e-6; r += dx, ++rIdx )
1995 {
1996 int pIdx = 0;
1997
1998 for( double phi = 0; phi <= phiMax + 1e-6; phi += dy, ++pIdx )
1999 {
2000 const VECTOR2D pos( r * std::cos( phi ), r * std::sin( phi ) );
2001
2002 if( aSrc.style == GRID_STYLE::DOTS )
2003 drawDotAt( pos, lattice.IsMajor( rIdx ), lattice.IsMajor( pIdx ) );
2004 else
2005 drawCrossAt( pos, lattice.IsMajor( rIdx ) && lattice.IsMajor( pIdx ), phi );
2006 }
2007 }
2008 }
2009 }
2010 else if( aSrc.kind == GRID_SOURCE::KIND::CARTESIAN )
2011 {
2012 BOX2D visible = gridScreenBBox( aSrc );
2013
2014 // One-pitch bleed so lines just off-screen still paint.
2015 visible.Inflate( dx, dy );
2016
2017 double xMin = visible.GetLeft();
2018 double xMax = visible.GetRight();
2019 double yMin = visible.GetTop();
2020 double yMax = visible.GetBottom();
2021
2022 int ixMin = (int) std::floor( xMin / dx );
2023 int ixMax = (int) std::ceil( xMax / dx );
2024 int iyMin = (int) std::floor( yMin / dy );
2025 int iyMax = (int) std::ceil( yMax / dy );
2026
2027 if( !aSrc.unbounded )
2028 {
2029 xMin = std::max( xMin, -aSrc.extent.x );
2030 xMax = std::min( xMax, aSrc.extent.x );
2031 yMin = std::max( yMin, -aSrc.extent.y );
2032 yMax = std::min( yMax, aSrc.extent.y );
2033
2034 ixMin = std::max( ixMin, -(int) ( aSrc.extent.x / dx ) );
2035 ixMax = std::min( ixMax, (int) ( aSrc.extent.x / dx ) );
2036 iyMin = std::max( iyMin, -(int) ( aSrc.extent.y / dy ) );
2037 iyMax = std::min( iyMax, (int) ( aSrc.extent.y / dy ) );
2038 }
2039
2040 if( aSrc.style == GRID_STYLE::LINES )
2041 {
2042 for( int ix = ixMin; ix <= ixMax; ++ix )
2043 {
2044 // Skip the line that would cover the world Y axis when axes are drawn.
2045 if( aSrc.axesEnabled && ix * dx + aSrc.origin.x == 0.0 )
2046 continue;
2047
2048 SetLineWidth( lattice.IsMajor( ix ) ? doubleMarker : marker );
2049 drawGridLine( VECTOR2D( ix * dx, yMin ), VECTOR2D( ix * dx, yMax ), color );
2050 }
2051
2052 for( int iy = iyMin; iy <= iyMax; ++iy )
2053 {
2054 if( aSrc.axesEnabled && iy * dy + aSrc.origin.y == 0.0 )
2055 continue;
2056
2057 SetLineWidth( lattice.IsMajor( iy ) ? doubleMarker : marker );
2058 drawGridLine( VECTOR2D( xMin, iy * dy ), VECTOR2D( xMax, iy * dy ), color );
2059 }
2060 }
2061 else
2062 {
2063 for( int ix = ixMin; ix <= ixMax; ++ix )
2064 {
2065 for( int iy = iyMin; iy <= iyMax; ++iy )
2066 {
2067 const VECTOR2D pos( ix * dx, iy * dy );
2068
2069 if( aSrc.style == GRID_STYLE::DOTS )
2070 drawDotAt( pos, lattice.IsMajor( ix ), lattice.IsMajor( iy ) );
2071 else
2072 drawCrossAt( pos, lattice.IsMajor( ix ) && lattice.IsMajor( iy ), 0.0 );
2073 }
2074 }
2075 }
2076 }
2077
2078 Restore();
2079
2080 // Outline the coverage, so a grid too sparse to render still reads as an area.
2081 if( !aSrc.unbounded )
2082 {
2083 SetIsFill( false );
2084 SetIsStroke( true );
2086 SetLineWidth( static_cast<float>( 1.0 / m_worldScale ) );
2087 drawGridCoverageShape( aSrc );
2088 }
2089}
2090
2091
2092void CAIRO_GAL_BASE::DrawGlyph( const KIFONT::GLYPH& aGlyph, int aNth, int aTotal )
2093{
2094 if( aGlyph.IsStroke() )
2095 {
2096 const KIFONT::STROKE_GLYPH& glyph = static_cast<const KIFONT::STROKE_GLYPH&>( aGlyph );
2097
2098 for( const std::vector<VECTOR2D>& pointList : glyph )
2099 drawPoly( pointList );
2100 }
2101 else if( aGlyph.IsOutline() )
2102 {
2103 const KIFONT::OUTLINE_GLYPH& glyph = static_cast<const KIFONT::OUTLINE_GLYPH&>( aGlyph );
2104
2105 if( aNth == 0 )
2106 {
2107 cairo_close_path( m_currentContext );
2108 flushPath();
2109
2110 cairo_new_path( m_currentContext );
2111 SetIsFill( true );
2112 SetIsStroke( false );
2113 }
2114
2115 // eventually glyphs should not be drawn as polygons at all,
2116 // but as bitmaps with antialiasing, this is just a stopgap measure
2117 // of getting some form of outline font display
2118
2119 glyph.Triangulate(
2120 [&]( const VECTOR2D& aVertex1, const VECTOR2D& aVertex2, const VECTOR2D& aVertex3 )
2121 {
2122 syncLineWidth();
2123
2124 const VECTOR2D p0 = roundp( xform( aVertex1 ) );
2125 const VECTOR2D p1 = roundp( xform( aVertex2 ) );
2126 const VECTOR2D p2 = roundp( xform( aVertex3 ) );
2127
2128 cairo_move_to( m_currentContext, p0.x, p0.y );
2129 cairo_line_to( m_currentContext, p1.x, p1.y );
2130 cairo_line_to( m_currentContext, p2.x, p2.y );
2131 cairo_close_path( m_currentContext );
2132 cairo_set_fill_rule( m_currentContext, CAIRO_FILL_RULE_EVEN_ODD );
2133 flushPath();
2134 cairo_fill( m_currentContext );
2135 } );
2136
2137 if( aNth == aTotal - 1 )
2138 {
2139 flushPath();
2140 SetIsFill( false );
2141 SetIsStroke( true );
2142 m_isElementAdded = true;
2143 }
2144 }
2145}
BOX2< VECTOR2D > BOX2D
Definition box2.h:915
Class that handles multitarget rendering (ie.
static double roundp(double x)
This class handle bitmap images in KiCad.
Definition bitmap_base.h:47
VECTOR2I GetSizePixels() const
int GetPPI() const
wxImage * GetImageData()
Definition bitmap_base.h:66
constexpr BOX2< Vec > & Inflate(coord_type dx, coord_type dy)
Inflates the rectangle horizontally by dx and vertically by dy.
Definition box2.h:553
constexpr coord_type GetLeft() const
Definition box2.h:225
constexpr coord_type GetRight() const
Definition box2.h:214
constexpr coord_type GetTop() const
Definition box2.h:226
constexpr coord_type GetBottom() const
Definition box2.h:219
static const WX_CURSOR_TYPE GetCursor(KICURSOR aCursorType, bool aHiDPI=false)
Get a cursor bundle (wx 3.3+) or appropriate cursor (older versions)
Definition cursors.cpp:399
double AsRadians() const
Definition eda_angle.h:119
virtual bool IsStroke() const
Definition glyph.h:40
virtual bool IsOutline() const
Definition glyph.h:39
void Triangulate(std::function< void(const VECTOR2I &aPt1, const VECTOR2I &aPt2, const VECTOR2I &aPt3)> aCallback) const
Definition glyph.cpp:134
void DrawGlyph(const KIFONT::GLYPH &aPolySet, int aNth, int aTotal) override
Draw a polygon representing a font glyph.
void blitCursor(wxMemoryDC &clientDC)
Blit cursor into the current screen.
cairo_surface_t * m_surface
Cairo surface.
Definition cairo_gal.h:388
void DrawArcSegment(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle, double aWidth, double aMaxError) override
Draw an arc segment.
cairo_matrix_t m_cairoWorldScreenMatrix
Cairo world to screen transform matrix.
Definition cairo_gal.h:383
void Flush() override
Force all remaining objects to be drawn.
void BeginDrawing() override
Start/end drawing functions, draw calls can be only made in between the calls to BeginDrawing()/EndDr...
Definition cairo_gal.cpp:93
double xform(double x)
void Restore() override
Restore the context.
unsigned int m_groupCounter
Counter used for generating group keys.
Definition cairo_gal.h:377
void Translate(const VECTOR2D &aTranslation) override
Translate the context.
void Save() override
Save the context.
void ClearCache() override
Delete all data created during caching of graphic items.
bool m_isElementAdded
Was an graphic element added ?
Definition cairo_gal.h:375
void DeleteGroup(int aGroupNumber) override
Delete the group from the memory.
std::deque< GROUP_ELEMENT > GROUP
A graphic group type definition.
Definition cairo_gal.h:371
void drawGridCross(const VECTOR2D &aPoint, const COLOR4D &aColor)
void DrawLine(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint) override
Draw a line.
void ClearScreen() override
Clear the screen.
void storePath()
Store the actual path.
void drawGridPoint(const VECTOR2D &aPoint, double aWidth, double aHeight, const COLOR4D &aColor)
void DrawGroup(int aGroupNumber) override
Draw the stored group.
void drawAxes(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint)
CAIRO_GAL_BASE(GAL_DISPLAY_OPTIONS &aDisplayOptions)
Definition cairo_gal.cpp:50
std::vector< cairo_surface_t * > m_imageSurfaces
List of surfaces that were created by painting images, to be cleaned up later.
Definition cairo_gal.h:391
void SetFillColor(const COLOR4D &aColor) override
Set the fill color.
GROUP * m_currentGroup
Currently used group.
Definition cairo_gal.h:378
void DrawCursor(const VECTOR2D &aCursorPosition) override
Draw the cursor.
const VECTOR2D roundp(const VECTOR2D &v)
void drawGridSources()
Render m_gridSources precedence-ascending; each bounded source paints its coverage out first,...
bool m_isGrouping
Is grouping enabled ?
Definition cairo_gal.h:374
static constexpr cairo_format_t GAL_FORMAT
Format used to store pixels.
Definition cairo_gal.h:395
void SetNegativeDrawMode(bool aSetting) override
Set negative draw mode in the renderer.
void ChangeGroupDepth(int aGroupNumber, int aDepth) override
Change the depth (Z-axis position) of the group.
int BeginGroup() override
Begin a group.
void SetLayerDepth(double aLayerDepth) override
Set the depth of the layer (position on the z-axis)
unsigned int getNewGroupNumber()
Return a valid key that can be used as a new group number.
cairo_matrix_t m_currentXform
Definition cairo_gal.h:384
void SetIsStroke(bool aIsStrokeEnabled) override
Enable/disable stroked outlines.
void EndGroup() override
End the group.
void DrawHoleWall(const VECTOR2D &aCenterPoint, double aHoleRadius, double aWallWidth) override
Draw a hole wall ring.
void drawPoly(const std::deque< VECTOR2D > &aPointList)
Drawing polygons & polylines is the same in Cairo, so here is the common code.
void Transform(const MATRIX3x3D &aTransformation) override
Transform the context.
void DrawRectangle(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint) override
Draw a rectangle.
void ChangeGroupColor(int aGroupNumber, const COLOR4D &aNewColor) override
Change the color used to draw the group.
void DrawEllipseArc(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aEndAngle) override
Draw an elliptical arc in world coordinates.
std::map< int, GROUP > m_groups
List of graphic groups.
Definition cairo_gal.h:376
void SetStrokeColor(const COLOR4D &aColor) override
Set the stroke color.
cairo_t * m_context
Cairo image.
Definition cairo_gal.h:387
void EndDrawing() override
End the drawing, needs to be called for every new frame.
Definition cairo_gal.cpp:99
void EnableDepthTest(bool aEnabled=false) override
void drawGridLine(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, const COLOR4D &aColor)
Draw a grid line (usually a simplified line function).
void DrawCurve(const VECTOR2D &startPoint, const VECTOR2D &controlPointA, const VECTOR2D &controlPointB, const VECTOR2D &endPoint, double aFilterValue=0.0) override
Draw a cubic bezier spline.
void Scale(const VECTOR2D &aScale) override
Scale the context.
std::vector< cairo_matrix_t > m_xformStack
Definition cairo_gal.h:393
void DrawPolygon(const std::deque< VECTOR2D > &aPointList) override
Draw a polygon.
Definition cairo_gal.h:123
cairo_matrix_t m_currentWorld2Screen
Definition cairo_gal.h:385
void DrawEllipse(const VECTOR2D &aCenterPoint, double aMajorRadius, double aMinorRadius, const EDA_ANGLE &aRotation) override
Draw a closed ellipse.
void SetLineWidth(float aLineWidth) override
Set the line width.
void arc_angles_xform_and_normalize(double &aStartAngle, double &aEndAngle)
Transform according to the rotation from m_currentWorld2Screen transform matrix for the start angle a...
double angle_xform(double aAngle)
Transform according to the rotation from m_currentWorld2Screen transform matrix.
void syncLineWidth(bool aForceWidth=false, double aWidth=0.0)
cairo_t * m_currentContext
Currently used Cairo context for drawing.
Definition cairo_gal.h:386
void DrawCircle(const VECTOR2D &aCenterPoint, double aRadius) override
Draw a circle using world coordinates.
void DrawSegment(const VECTOR2D &aStartPoint, const VECTOR2D &aEndPoint, double aWidth) override
Draw a rounded segment.
void DrawArc(const VECTOR2D &aCenterPoint, double aRadius, const EDA_ANGLE &aStartAngle, const EDA_ANGLE &aAngle) override
Draw an arc.
void SetIsFill(bool aIsFillEnabled) override
Enable/disable fill.
void DrawBitmap(const BITMAP_BASE &aBitmap, double alphaBlend=1.0) override
Draw a bitmap image.
@ CMD_SET_STROKE
Enable/disable stroking.
Definition cairo_gal.h:344
@ CMD_SAVE
Save the transformation matrix.
Definition cairo_gal.h:354
@ CMD_SCALE
Scale the context.
Definition cairo_gal.h:353
@ CMD_SET_LINE_WIDTH
Set the line width.
Definition cairo_gal.h:347
@ CMD_SET_FILL
Enable/disable filling.
Definition cairo_gal.h:343
@ CMD_CALL_GROUP
Call a group.
Definition cairo_gal.h:356
@ CMD_ROTATE
Rotate the context.
Definition cairo_gal.h:351
@ CMD_STROKE_PATH
Set the stroke path.
Definition cairo_gal.h:348
@ CMD_TRANSLATE
Translate the context.
Definition cairo_gal.h:352
@ CMD_SET_FILLCOLOR
Set the fill color.
Definition cairo_gal.h:345
@ CMD_FILL_PATH
Set the fill path.
Definition cairo_gal.h:349
@ CMD_RESTORE
Restore the transformation matrix.
Definition cairo_gal.h:355
@ CMD_SET_STROKECOLOR
Set the stroke color.
Definition cairo_gal.h:346
void ResizeScreen(int aWidth, int aHeight) override
Resizes the canvas.
void Rotate(double aAngle) override
Rotate the context.
void DrawSegmentChain(const std::vector< VECTOR2D > &aPointList, double aWidth) override
Draw a chain of rounded segments.
void drawGridSource(const GRID_SOURCE &aSrc)
Paint one source: coverage wash, grid content, then hairline.
void DrawGrid() override
Draw the grid.
void EndGroup() override
End the group.
void deinitSurface()
Destroy Cairo surfaces when are not needed anymore.
void PostPaint(wxPaintEvent &aEvent)
Post an event to m_paint_listener.
bool Show(bool aShow) override
Show/hide the GAL canvas.
void SetTarget(RENDER_TARGET aTarget) override
Set the target for rendering.
void skipMouseEvent(wxMouseEvent &aEvent)
Mouse event handler, forwards the event to the child.
unsigned int m_overlayBuffer
Handle to the overlay buffer.
Definition cairo_gal.h:539
unsigned int m_bufferSize
Size of buffers cairoOutput, bitmapBuffers.
Definition cairo_gal.h:549
CAIRO_GAL(GAL_DISPLAY_OPTIONS &aDisplayOptions, wxWindow *aParent, wxEvtHandler *aMouseListener=nullptr, wxEvtHandler *aPaintListener=nullptr, const wxString &aName=wxT("CairoCanvas"))
void initSurface()
Prepare Cairo surfaces for drawing.
int BeginGroup() override
Begin a group.
unsigned char * m_wxOutput
wxImage compatible buffer
Definition cairo_gal.h:550
bool m_isInitialized
Are Cairo image & surface ready to use.
Definition cairo_gal.h:556
bool m_validCompositor
Compositor initialization flag.
Definition cairo_gal.h:543
unsigned int m_mainBuffer
Handle to the main buffer.
Definition cairo_gal.h:538
int m_stride
Stride value for Cairo.
Definition cairo_gal.h:554
void onSetNativeCursor(wxSetCursorEvent &aEvent)
Give the correct cursor image when the native widget asks for it.
void EndDrawing() override
End the drawing, needs to be called for every new frame.
void skipGestureEvent(wxGestureEvent &aEvent)
Skip the gesture event to the parent.
void allocateBitmaps()
Allocate the bitmaps for drawing.
void onPaint(wxPaintEvent &aEvent)
Paint event handler.
void StartNegativesLayer() override
Begins rendering in a new layer that will be copied to the main layer in EndNegativesLayer().
void ResizeScreen(int aWidth, int aHeight) override
Resize the canvas.
RENDER_TARGET GetTarget() const override
Get the currently used target for rendering.
void setCompositor()
Prepare the compositor.
bool SetNativeCursorStyle(KICURSOR aCursor, bool aHiDPI) override
Set the cursor in the native panel.
unsigned char * m_bitmapBuffer
Storage of the Cairo image.
Definition cairo_gal.h:553
wxEvtHandler * m_mouseListener
Mouse listener.
Definition cairo_gal.h:547
void ClearTarget(RENDER_TARGET aTarget) override
Clear the target for rendering.
bool updatedGalDisplayOptions(const GAL_DISPLAY_OPTIONS &aOptions) override
Cairo-specific update handlers.
void BeginDrawing() override
Start/end drawing functions, draw calls can be only made in between the calls to BeginDrawing()/EndDr...
wxEvtHandler * m_paintListener
Paint listener.
Definition cairo_gal.h:548
unsigned int m_tempBuffer
Handle to the temp buffer.
Definition cairo_gal.h:540
RENDER_TARGET m_currentTarget
Current rendering target.
Definition cairo_gal.h:542
wxWindow * m_parentWindow
Parent window.
Definition cairo_gal.h:546
void EndDiffLayer() override
Ends rendering of a differential layer.
std::shared_ptr< CAIRO_COMPOSITOR > m_compositor
Object for layers compositing.
Definition cairo_gal.h:537
void deleteBitmaps()
Allocate the bitmaps for drawing.
WX_CURSOR_TYPE m_currentwxCursor
wx cursor showing the current native cursor
Definition cairo_gal.h:559
void EndNegativesLayer() override
Ends rendering of a negatives layer and draws it to the main layer.
void StartDiffLayer() override
Begins rendering of a differential layer.
unsigned int m_savedBuffer
Handle to buffer to restore after rendering to temp buffer.
Definition cairo_gal.h:541
std::vector< wxRect > m_overlayExclusions
Areas the frame blit must leave alone.
Definition cairo_gal.h:561
A color representation with 4 components: red, green, blue, alpha.
Definition color4d.h:101
double r
Red component.
Definition color4d.h:391
double g
Green component.
Definition color4d.h:392
COLOR4D Darkened(double aFactor) const
Return a color that is darker by a given factor, without modifying object.
Definition color4d.h:279
double a
Alpha component.
Definition color4d.h:394
static const COLOR4D BLACK
Definition color4d.h:404
double b
Blue component.
Definition color4d.h:393
GAL_ANTIALIASING_MODE antialiasing_mode
void SetGridColor(const COLOR4D &aGridColor)
Set the grid color.
virtual void SetLayerDepth(double aLayerDepth)
Set the depth of the layer (position on the z-axis)
bool IsCursorEnabled() const
Return information about cursor visibility.
MATRIX3x3D m_worldScreenMatrix
World transformation.
VECTOR2D GetVisibleGridSize() const
Return the visible grid size in x and y directions.
COLOR4D gridSourceColor(const GRID_SOURCE &aSrc) const
The colour a grid source is drawn in, brightened while it is being edited.
MATRIX3x3D m_screenWorldMatrix
Screen transformation.
bool m_axesEnabled
Should the axes be drawn.
float m_gridLineWidth
Line width of the grid.
VECTOR2I m_screenSize
Screen size in screen (wx logical) coordinates.
void normalize(T &a, T &b)
Ensure that the first element is smaller than the second.
VECTOR2D m_depthRange
Range of the depth.
VECTOR2D ToScreen(const VECTOR2D &aPoint) const
Compute the point position in screen coordinates from given world coordinates.
virtual bool SetNativeCursorStyle(KICURSOR aCursor, bool aHiDPI)
Set the cursor in the native panel.
GRID_STYLE m_gridStyle
Grid display style.
COLOR4D m_axesColor
Color of the axes.
float m_lineWidth
The line width.
GRID_LATTICE gridLattice(const GRID_SOURCE &aSrc) const
Thin a grid source's pitch out until its lines are far enough apart on screen.
void drawGridCoverageShape(const GRID_SOURCE &aSrc)
Draw the area a grid source covers in its own frame with the current fill and stroke.
virtual void SetLineWidth(float aLineWidth)
Set the line width.
VECTOR2D m_gridSize
The grid size.
COLOR4D getCursorColor() const
Get the actual cursor color to draw.
COLOR4D m_fillColor
The fill color.
double m_worldUnitLength
The unit length of the world coordinates [inch].
virtual bool updatedGalDisplayOptions(const GAL_DISPLAY_OPTIONS &aOptions)
Handle updating display options.
void SetAxesColor(const COLOR4D &aAxesColor)
Set the axes color.
BOX2D gridScreenBBox(const GRID_SOURCE &aSrc) const
VECTOR2D m_cursorPosition
Current cursor position (world coordinates)
int m_gridTick
Every tick line gets the double width.
double m_worldScale
The scale factor world->screen.
VECTOR2D m_gridOrigin
The grid origin.
KICURSOR m_currentNativeCursor
Current cursor.
bool m_isFillEnabled
Is filling of graphic objects enabled ?
virtual void ComputeWorldScreenMatrix()
Compute the world <-> screen transformation matrix.
COLOR4D m_gridColor
Color of the grid.
COLOR4D m_strokeColor
The color of the outlines.
bool m_isStrokeEnabled
Are the outlines stroked ?
std::vector< GRID_SOURCE > m_gridSources
Sources overlayed on the display grid.
GAL_DISPLAY_OPTIONS & m_options
bool m_gridVisibility
Should the grid be shown.
virtual void SetTarget(RENDER_TARGET aTarget)
Set the target for rendering.
KIGFX::CROSS_HAIR_MODE m_crossHairMode
Crosshair drawing mode.
GAL(GAL_DISPLAY_OPTIONS &aOptions)
T m_data[3][3]
Definition matrix3x3.h:61
Represent a polyline containing arcs as well as line segments: A chain of connected line and/or arc s...
bool IsClosed() const override
int PointCount() const
Return the number of points (vertices) in this line chain.
const VECTOR2I & CPoint(int aIndex) const
Return a reference to a given point in the line chain.
Represent a set of closed polygons.
int OutlineCount() const
Return the number of outlines in the set.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
@ BLUE
Definition color4d.h:52
KICURSOR
Definition cursors.h:41
@ RADIANS_T
Definition eda_angle.h:31
MATRIX3x3< double > MATRIX3x3D
Definition matrix3x3.h:469
The Cairo implementation of the graphics abstraction layer.
Definition eda_group.h:30
constexpr double GRID_DIM_ALPHA
Opacity of the background wash laid over a selected grid item's area, to fade the grids showing throu...
@ DOTS
Use dots for the grid.
@ LINES
Use lines for the grid.
RENDER_TARGET
RENDER_TARGET: Possible rendering targets.
Definition definitions.h:32
@ TARGET_NONCACHED
Auxiliary rendering target (noncached)
Definition definitions.h:34
@ TARGET_TEMP
Temporary target for drawing in separate layer.
Definition definitions.h:36
@ TARGET_CACHED
Main rendering target (cached)
Definition definitions.h:33
@ TARGET_OVERLAY
Items that may change while the view stays the same (noncached)
Definition definitions.h:35
constexpr double GRID_EDGE_DARKEN
How far the hairline round a grid's coverage sits below its own colour.
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
const int scale
unsigned priority
Higher wins where grids overlap.
double orientation
Rotation about origin, radians CCW.
VECTOR2D extent
Cartesian: (dx, dy) = size/2 from origin.
VECTOR2D pitch
Cartesian: (dx, dy); polar: (dr, dPhi rad).
VECTOR2D origin
Type definition for an graphics group element.
Definition cairo_gal.h:361
union KIGFX::CAIRO_GAL_BASE::GROUP_ELEMENT::@224355003013256125016006047023123235275076033053 m_Argument
GRAPHICS_COMMAND m_Command
Command to execute.
Definition cairo_gal.h:362
cairo_path_t * m_CairoPath
Pointer to a Cairo path.
Definition cairo_gal.h:368
double DblArg[MAX_CAIRO_ARGUMENTS]
Arguments for Cairo commands.
Definition cairo_gal.h:364
The spacing of a grid source's lattice as it is drawn at the current zoom.
VECTOR2D extent
as GRID_SOURCE::extent, with an unbounded polar grid reaching past the screen
VECTOR2D pitch
along x and y, or of radius and angle for a polar grid
Render-time projection of a grid: GRID_GEOMETRY (kind/origin/pitch/orientation/extent) plus rendering...
unsigned tick
Major-tick interval (0 = inherit default).
bool axesEnabled
Skip grid lines coincident with the world axes (only meaningful for unbounded cartesian).
bool highlighted
Render with edit-mode emphasis (selected grid).
bool unbounded
No extent; visible range derived from screen corners.
int radius
VECTOR2I end
#define M_PI
void RotatePoint(int *pX, int *pY, const EDA_ANGLE &aAngle)
Calculate the new point of coord coord pX, pY, for a rotation center 0, 0.
Definition trigo.cpp:227
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
VECTOR2I ToVECTOR2I(const wxSize &aSize)
Definition vector2wx.h:26