animate/canvas/
cairoimpl.rs

1#![allow(clippy::many_single_char_names)]
2#![allow(unused_imports)]
3#![allow(unused_variables)]
4#![allow(dead_code)]
5#![cfg(not(target_arch = "wasm32"))]
6
7use crate::{
8    BaseLine, CanvasContext, Color, Direction, Gradient, GradientType, LineCap, LineJoin,
9    LinearGradient, PatternExtend, Point, RadialGradient, Rect, RgbaColor, Size, TextAlign,
10    TextMetrics, TextStyle, TextWeight,
11};
12use cairo::{self, FontFace, FontSlant, FontWeight, ImageSurface, Surface, SurfacePattern};
13use std::{any::Any, cell::RefCell};
14
15#[derive(Debug, Clone)]
16pub struct Pattern {
17    pub extend: PatternExtend,
18    pub inner: cairo::SurfacePattern,
19}
20
21impl Pattern {
22    // Create pattern
23    pub fn new(extend: PatternExtend, surface: &cairo::Surface) -> Self {
24        Self {
25            extend,
26            inner: SurfacePattern::create(surface),
27        }
28    }
29}
30
31#[derive(Clone)]
32enum Paint {
33    None,
34    Solid(Color),
35    Gradient(Gradient),
36    Pattern(Pattern),
37}
38
39impl Default for Paint {
40    fn default() -> Self {
41        Paint::None
42    }
43}
44
45#[derive(Default, Clone)]
46struct CanvasState {
47    stroke: Paint,
48    fill: Paint,
49}
50
51pub struct Canvas<'a> {
52    ctx: &'a cairo::Context,
53    state: RefCell<CanvasState>,
54}
55
56impl<'a> Canvas<'a> {
57    #[allow(dead_code)]
58    pub fn new(ctx: &'a cairo::Context) -> Self {
59        Self {
60            ctx,
61            state: Default::default(),
62        }
63    }
64
65    fn handle_paint(&self, paint: &Paint) -> bool {
66        match paint {
67            Paint::Solid(value) => {
68                let value = *value;
69                let color: RgbaColor = value.into();
70                self.ctx.set_source_rgba(
71                    color.red as f64 / 255.,
72                    color.green as f64 / 255.,
73                    color.blue as f64 / 255.,
74                    color.alpha as f64 / 255.,
75                );
76
77                true
78            }
79            Paint::Gradient(value) => {
80                match value.kind {
81                    GradientType::Linear(params) => {
82                        let LinearGradient { x0, y0, x1, y1 } = params;
83                        let gradient = cairo::LinearGradient::new(x0, y0, x1, y1);
84                        let stops = value.stops.borrow();
85                        for stop in stops.iter() {
86                            let RgbaColor {
87                                red,
88                                green,
89                                blue,
90                                alpha,
91                            } = stop.color.into();
92
93                            gradient.add_color_stop_rgba(
94                                stop.offset,
95                                red as f64 / 255.,
96                                green as f64 / 255.,
97                                blue as f64 / 255.,
98                                alpha as f64 / 255.,
99                            );
100                        }
101                        self.ctx.set_source(&gradient);
102                    }
103                    GradientType::Radial(params) => {
104                        let RadialGradient {
105                            x0,
106                            y0,
107                            r0,
108                            x1,
109                            y1,
110                            r1,
111                        } = params;
112                        let gradient = cairo::RadialGradient::new(x0, y0, r0, x1, y1, r1);
113                        let stops = value.stops.borrow();
114                        for stop in stops.iter() {
115                            let RgbaColor {
116                                red,
117                                green,
118                                blue,
119                                alpha,
120                            } = stop.color.into();
121
122                            gradient.add_color_stop_rgba(
123                                stop.offset,
124                                red as f64 / 255.,
125                                green as f64 / 255.,
126                                blue as f64 / 255.,
127                                alpha as f64 / 255.,
128                            );
129                        }
130                        self.ctx.set_source(&gradient);
131                    }
132                }
133
134                true
135            }
136            Paint::Pattern(value) => {
137                let extend = match value.extend {
138                    PatternExtend::None => cairo::Extend::None,
139                    PatternExtend::Repeat => cairo::Extend::Repeat,
140                    PatternExtend::Reflect => cairo::Extend::Reflect,
141                    PatternExtend::Pad => cairo::Extend::Pad,
142                };
143
144                value.inner.set_extend(extend);
145                self.ctx.set_source(&value.inner);
146
147                true
148            }
149            Paint::None => false,
150        }
151    }
152}
153
154impl<'a> CanvasContext for Canvas<'a> {
155    type Pattern = Pattern;
156    // fn get_current_transform(&self) -> Matrix;
157
158    // fn set_current_transform(&self, value: Matrix<f64>) {
159    //     // self.ctx.get_matrix()
160    //     unimplemented!()
161    // }
162
163    fn get_direction(&self) -> Direction {
164        unimplemented!()
165    }
166
167    fn set_direction(&self, value: Direction) -> String {
168        info!("NOT IMPLEMENTED");
169        unimplemented!()
170    }
171
172    fn set_fill_color(&self, value: Color) {
173        let mut state = self.state.borrow_mut();
174        state.fill = Paint::Solid(value);
175
176        let RgbaColor {
177            red,
178            green,
179            blue,
180            alpha,
181        } = value.into();
182        self.ctx.set_source_rgba(
183            red as f64 / 255.,
184            green as f64 / 255.,
185            blue as f64 / 255.,
186            alpha as f64 / 255.,
187        );
188    }
189
190    fn set_fill_gradient(&self, value: &Gradient) {
191        let mut state = self.state.borrow_mut();
192        state.fill = Paint::Gradient(value.clone());
193
194        match value.kind {
195            GradientType::Linear(params) => {
196                let LinearGradient { x0, y0, x1, y1 } = params;
197                let gradient = cairo::LinearGradient::new(x0, y0, x1, y1);
198                let stops = value.stops.borrow();
199                for stop in stops.iter() {
200                    let RgbaColor {
201                        red,
202                        green,
203                        blue,
204                        alpha,
205                    } = stop.color.into();
206
207                    gradient.add_color_stop_rgba(
208                        stop.offset,
209                        red as f64 / 255.,
210                        green as f64 / 255.,
211                        blue as f64 / 255.,
212                        alpha as f64 / 255.,
213                    );
214                }
215                self.ctx.set_source(&gradient);
216            }
217            GradientType::Radial(params) => {
218                let RadialGradient {
219                    x0,
220                    y0,
221                    r0,
222                    x1,
223                    y1,
224                    r1,
225                } = params;
226                let gradient = cairo::RadialGradient::new(x0, y0, r0, x1, y1, r1);
227                let stops = value.stops.borrow();
228                for stop in stops.iter() {
229                    let RgbaColor {
230                        red,
231                        green,
232                        blue,
233                        alpha,
234                    } = stop.color.into();
235
236                    gradient.add_color_stop_rgba(
237                        stop.offset,
238                        red as f64 / 255.,
239                        green as f64 / 255.,
240                        blue as f64 / 255.,
241                        alpha as f64 / 255.,
242                    );
243                }
244                self.ctx.set_source(&gradient);
245            }
246        }
247    }
248
249    fn set_fill_pattern(&self, value: &Self::Pattern) {
250        let mut state = self.state.borrow_mut();
251        state.fill = Paint::Pattern(value.clone());
252
253        let extend = match value.extend {
254            PatternExtend::None => cairo::Extend::None,
255            PatternExtend::Repeat => cairo::Extend::Repeat,
256            PatternExtend::Reflect => cairo::Extend::Reflect,
257            PatternExtend::Pad => cairo::Extend::Pad,
258        };
259
260        value.inner.set_extend(extend);
261        self.ctx.set_source(&value.inner);
262    }
263
264    fn get_filter(&self) -> String {
265        unimplemented!()
266    }
267
268    fn set_filter(&self, value: &str) {
269        unimplemented!()
270    }
271
272    fn get_font(&self) -> String {
273        // self.ctx.get_font_face(font_face)
274        unimplemented!()
275    }
276
277    fn set_font(&self, family: &str, style: TextStyle, weight: TextWeight, size: f64) {
278        let slant = match style {
279            TextStyle::Italic => FontSlant::Italic,
280            TextStyle::Normal => FontSlant::Normal,
281            TextStyle::Oblique => FontSlant::Oblique,
282        };
283
284        let weight = match weight {
285            TextWeight::Bold => FontWeight::Bold,
286            _ => FontWeight::Normal,
287        };
288
289        self.ctx.select_font_face(family, slant, weight);
290        self.ctx.set_font_size(size);
291    }
292
293    fn get_global_alpha(&self) -> f64 {
294        unimplemented!()
295    }
296
297    fn set_global_alpha(&self, value: f64) {
298        unimplemented!()
299    }
300
301    fn get_global_composite_operation(&self) -> String {
302        unimplemented!()
303    }
304
305    fn set_global_composite_operation(&self, value: &str) {
306        unimplemented!()
307    }
308
309    // Whether images and patterns on this canvas will be smoothed when this canvas is scaled.
310    // imageSmoothingEnabled
311    fn is_image_smoothing_enabled(&self) -> bool {
312        unimplemented!()
313    }
314
315    fn set_image_smoothing(&self, value: bool) {
316        unimplemented!()
317    }
318
319    // fn get_image_smoothing_quality(&self) -> String {
320    //     unimplemented!()
321    // }
322
323    // fn set_image_smoothing_quality(&self, value: &str) {
324    //     unimplemented!()
325    // }
326
327    fn get_line_cap(&self) -> LineCap {
328        match self.ctx.get_line_cap() {
329            cairo::LineCap::Round => LineCap::Round,
330            cairo::LineCap::Square => LineCap::Square,
331            _ => LineCap::Butt, // Return default value
332        }
333    }
334
335    fn set_line_cap(&self, value: LineCap) {
336        let value = match value {
337            LineCap::Round => cairo::LineCap::Round,
338            LineCap::Square => cairo::LineCap::Square,
339            LineCap::Butt => cairo::LineCap::Butt,
340        };
341        self.ctx.set_line_cap(value)
342    }
343
344    fn get_line_dash_offset(&self) -> f64 {
345        let (_, result) = self.ctx.get_dash();
346        result
347    }
348
349    fn set_line_dash_offset(&self, value: f64) {
350        let (dash, _) = self.ctx.get_dash();
351        self.ctx.set_dash(&dash, value);
352    }
353
354    fn get_line_join(&self) -> LineJoin {
355        match self.ctx.get_line_join() {
356            cairo::LineJoin::Bevel => LineJoin::Bevel,
357            cairo::LineJoin::Round => LineJoin::Round,
358            _ => LineJoin::Miter,
359        }
360    }
361
362    fn set_line_join(&self, value: LineJoin) {
363        let value = match value {
364            LineJoin::Bevel => cairo::LineJoin::Bevel,
365            LineJoin::Round => cairo::LineJoin::Round,
366            LineJoin::Miter => cairo::LineJoin::Miter,
367        };
368        self.ctx.set_line_join(value)
369    }
370
371    fn get_line_width(&self) -> f64 {
372        self.ctx.get_line_width()
373    }
374
375    fn set_line_width(&self, value: f64) {
376        self.ctx.set_line_width(value);
377    }
378
379    fn get_miter_limit(&self) -> f64 {
380        self.ctx.get_miter_limit()
381    }
382
383    fn set_miter_limit(&self, value: f64) {
384        self.ctx.set_miter_limit(value);
385    }
386
387    fn get_shadow_blur(&self) -> f64 {
388        unimplemented!()
389    }
390
391    fn set_shadow_blur(&self, value: f64) {
392        unimplemented!()
393    }
394
395    fn get_shadow_color(&self) -> Color {
396        unimplemented!()
397    }
398    fn set_shadow_color(&self, value: Color) {
399        unimplemented!()
400    }
401
402    fn get_shadow_offset_x(&self) -> f64 {
403        unimplemented!()
404    }
405    fn set_shadow_offset_x(&self, value: f64) {
406        unimplemented!()
407    }
408
409    fn get_shadow_offset_y(&self) -> f64 {
410        unimplemented!()
411    }
412
413    fn set_shadow_offset_y(&self, value: f64) {
414        unimplemented!()
415    }
416
417    fn set_stroke_color(&self, value: Color) {
418        let mut state = self.state.borrow_mut();
419        state.stroke = Paint::Solid(value);
420    }
421
422    fn set_stroke_gradient(&self, value: &Gradient) {
423        let mut state = self.state.borrow_mut();
424        state.stroke = Paint::Gradient(value.clone());
425    }
426
427    fn set_stroke_pattern(&self, value: &Self::Pattern) {
428        let mut state = self.state.borrow_mut();
429        state.stroke = Paint::Pattern(value.clone());
430    }
431
432    fn get_text_align(&self) -> TextAlign {
433        unimplemented!()
434    }
435
436    fn set_text_align(&self, value: TextAlign) {
437        //TODO: complete it
438    }
439
440    fn get_text_baseline(&self) -> BaseLine {
441        unimplemented!()
442    }
443
444    fn set_text_baseline(&self, value: BaseLine) {
445        //TODO: complete it
446    }
447
448    // anticlockwise: bool = false
449    fn arc(
450        &self,
451        x: f64,
452        y: f64,
453        radius: f64,
454        start_angle: f64,
455        end_angle: f64,
456        anticlockwise: bool,
457    ) {
458        if anticlockwise {
459            self.ctx.arc_negative(x, y, radius, start_angle, end_angle);
460        } else {
461            self.ctx.arc(x, y, radius, start_angle, end_angle);
462        }
463    }
464
465    fn arc_to(&self, x1: f64, y1: f64, x2: f64, y2: f64, radius: f64) {
466        unimplemented!()
467    }
468
469    fn begin_path(&self) {
470        self.ctx.new_path();
471    }
472
473    fn bezier_curve_to(&self, cp1x: f64, cp1y: f64, cp2x: f64, cp2y: f64, x: f64, y: f64) {
474        self.ctx.curve_to(cp1x, cp1y, cp2x, cp2y, x, y);
475    }
476
477    // FIXME: seems code correct but it should clear with transpatency but it black (((
478    fn clear_rect(&self, x: f64, y: f64, width: f64, height: f64) {
479        self.ctx.new_path();
480        self.ctx.save();
481        self.ctx.set_source_rgba(0., 0., 0., 0.);
482        self.ctx.set_operator(cairo::Operator::Clear);
483        self.ctx.rectangle(x, y, width, height);
484        self.ctx.fill();
485        self.ctx.restore();
486    }
487
488    // [path_OR_winding: dynamic, winding: String]
489    // fn clip(path_OR_winding: dynamic, winding: String); // TODO:
490    fn close_path(&self) {
491        self.ctx.close_path();
492    }
493
494    // @Creates('ImageData|=Object')
495    // [int? sh_OR_sw, dynamic imageDataColorSettings_OR_sh, Map? imageDataColorSettings]
496    // fn createImageData(data_OR_imagedata_OR_sw: dynamic, sh_OR_sw: int, imageDataColorSettings_OR_sh: dynamic, imageDataColorSettings: Map) -> ImageData; // TODO:
497
498    // fn createImageDataFromImageData(imagedata: ImageData) -> ImageData; // TODO:
499
500    // [Element? element]
501    // fn drawFocusIfNeeded(element_OR_path: dynamic, element: Element); // TODO:
502
503    // Draws an image from a CanvasImageSource to this canvas.
504    // @JSName('drawImage')
505    // fn drawImage(source: CanvasImageSource, destX: f64, destY: f64); // TODO:
506
507    // Draws an image from a CanvasImageSource to an area of this canvas.
508    // @JSName('drawImage')
509    // fn drawImageScaled(source: CanvasImageSource, destX: f64, destY: f64, destWidth: f64, destHeight: f64); // TODO:
510
511    // Draws an image from a CanvasImageSource to an area of this canvas.
512    // @JSName('drawImage')
513    // fn drawImageScaledFromSource(source: CanvasImageSource, sourceX: f64, sourceY: f64, sourceWidth: f64, sourceHeight: f64, destX: f64, destY: f64, destWidth: f64, destHeight: f64); // TODO:
514
515    // Draws an image from a CanvasImageSource to an area of this canvas.
516    // {Rectangle<f64>? sourceRect}
517    // fn drawImageToRect(source: CanvasImageSource, destRect: Rectangle<f64>, sourceRect: Rectangle<f64>); // TODO:
518
519    fn ellipse(
520        &self,
521        x: f64,
522        y: f64,
523        radius_x: f64,
524        radius_y: f64,
525        rotation: f64,
526        start_angle: f64,
527        end_angle: f64,
528        anticlockwise: bool,
529    ) {
530        self.ctx.save();
531
532        self.ctx.translate(x, y);
533        self.ctx.scale(1., radius_y / radius_x);
534        self.ctx.rotate(rotation);
535        self.ctx.translate(-x, -y);
536
537        self.ctx.arc(x, y, radius_x, start_angle, end_angle);
538
539        self.ctx.restore();
540        // stroke later to prevent stroke ugly transformation
541    }
542
543    // [dynamic path_OR_winding, String? winding]
544    // fn fill(path_OR_winding: dynamic, winding: String); // TODO:
545
546    fn fill(&self) {
547        let state = self.state.borrow();
548        if self.handle_paint(&state.fill) {
549            self.ctx.fill_preserve();
550        }
551    }
552
553    fn fill_rect(&self, x: f64, y: f64, width: f64, height: f64) {
554        let state = self.state.borrow();
555        if self.handle_paint(&state.fill) {
556            self.ctx.new_path();
557            self.ctx.rectangle(x, y, width, height);
558            self.ctx.fill();
559        }
560    }
561
562    // Draws text to the canvas.
563    fn fill_text(&self, text: &str, x: f64, y: f64) {
564        let state = self.state.borrow();
565        if self.handle_paint(&state.fill) {
566            self.ctx.new_path();
567            self.ctx.save();
568            self.ctx.move_to(x, y);
569            self.ctx.text_path(text);
570            self.ctx.fill();
571            self.ctx.restore();
572        }
573    }
574
575    // fn getContextAttributes() -> Map; // TODO:
576
577    // @Creates('ImageData|=Object')
578    // fn getImageData(sx: i64, sy: i64, sw: i64, sh: i64) -> ImageData; // TODO:
579
580    fn get_line_dash(&self) -> Vec<f64> {
581        let (result, _) = self.ctx.get_dash();
582        result
583    }
584
585    // [dynamic winding_OR_y, String? winding]
586    // fn isPointInPath(path_OR_x: dynamic, x_OR_y: f64, winding_OR_y: dynamic, winding: String) -> bool; // TODO:
587
588    // [f64? y]
589    // fn isPointInStroke(path_OR_x: dynamic, x_OR_y: f64, y: f64) -> bool; // TODO:
590    fn line_to(&self, x: f64, y: f64) {
591        self.ctx.line_to(x, y);
592    }
593
594    fn measure_text(&self, text: &str) -> TextMetrics {
595        let ext = self.ctx.text_extents(text);
596        TextMetrics {
597            width: ext.width,
598            height: ext.height,
599        }
600    }
601
602    fn move_to(&self, x: f64, y: f64) {
603        self.ctx.move_to(x, y);
604    }
605
606    // [int? dirtyX, int? dirtyY, int? dirtyWidth, int? dirtyHeight]
607    // fn putImageData(imagedata: ImageData, dx: i64, dy: i64, dirtyX: i64, dirtyY: i64, dirtyWidth: i64, dirtyHeight: i64); // TODO:
608    fn quadratic_curve_to(&self, cpx: f64, cpy: f64, x: f64, y: f64) {
609        // A quadratic Bezier can be always represented by a cubic one by
610        // applying the degree elevation algorithm. The resulted cubic representation
611        // will share its anchor points with the original quadratic,
612        // while the control points will be at 2/3 of the quadratic handle segments:
613
614        // C1 = (2·C + P1)/3
615        // C2 = (2·C + P2)/3
616
617        let (x0, y0) = self.ctx.get_current_point();
618        self.ctx.curve_to(
619            2.0 / 3.0 * cpx + 1.0 / 3.0 * x0,
620            2.0 / 3.0 * cpy + 1.0 / 3.0 * y0,
621            2.0 / 3.0 * cpx + 1.0 / 3.0 * x,
622            2.0 / 3.0 * cpy + 1.0 / 3.0 * y,
623            x,
624            y,
625        );
626    }
627
628    fn rect(&self, x: f64, y: f64, width: f64, height: f64) {
629        self.ctx.rectangle(x, y, width, height);
630    }
631
632    fn reset_transform(&self) {
633        unimplemented!()
634    }
635
636    fn restore(&self) {
637        self.ctx.restore();
638    }
639
640    fn rotate(&self, angle: f64) {
641        self.ctx.rotate(angle);
642    }
643
644    fn save(&self) {
645        self.ctx.save();
646    }
647
648    fn scale(&self, x: f64, y: f64) {
649        self.ctx.scale(x, y);
650    }
651
652    // [Path2D? path]
653    // fn scrollPathIntoView(path: Path2D); // TODO:
654
655    fn set_line_dash(&self, dash: &Vec<f64>) {
656        let (_, offset) = self.ctx.get_dash();
657        self.ctx.set_dash(dash, offset);
658    }
659
660    fn set_transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
661        let m = cairo::Matrix::new(a, b, c, d, e, f);
662        self.ctx.transform(m);
663    }
664
665    fn stroke(&self) {
666        let state = self.state.borrow();
667        if self.handle_paint(&state.stroke) {
668            self.ctx.stroke_preserve();
669        }
670    }
671
672    fn stroke_rect(&self, x: f64, y: f64, width: f64, height: f64) {
673        let state = self.state.borrow();
674        if self.handle_paint(&state.stroke) {
675            self.ctx.new_path();
676            self.ctx.rectangle(x, y, width, height);
677            self.ctx.stroke();
678        }
679    }
680
681    fn stroke_text(&self, text: &str, x: f64, y: f64) {
682        let state = self.state.borrow();
683        if self.handle_paint(&state.stroke) {
684            self.ctx.new_path();
685            self.ctx.save();
686            self.ctx.move_to(x, y);
687            self.ctx.text_path(text);
688            self.ctx.stroke();
689            self.ctx.restore();
690        }
691    }
692
693    fn transform(&self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) {
694        let m = cairo::Matrix::new(a, b, c, d, e, f);
695        self.ctx.transform(m);
696    }
697
698    fn translate(&self, x: f64, y: f64) {
699        self.ctx.translate(x, y);
700    }
701}