oxygengine_composite_renderer_backend_web/
lib.rs

1extern crate oxygengine_composite_renderer as renderer;
2#[macro_use]
3extern crate oxygengine_core as core;
4extern crate oxygengine_backend_web as backend;
5
6use backend::closure::WebClosure;
7use core::{
8    assets::{asset::AssetId, database::AssetsDatabase},
9    error::*,
10    Scalar,
11};
12use js_sys::{Array, Uint8Array};
13use renderer::{
14    composite_renderer::*, font_asset_protocol::FontAsset, font_face_asset_protocol::FontFaceAsset,
15    jpg_image_asset_protocol::JpgImageAsset, math::*, png_image_asset_protocol::PngImageAsset,
16    svg_image_asset_protocol::SvgImageAsset,
17};
18use std::{cell::RefCell, collections::HashMap, rc::Rc};
19use wasm_bindgen::{prelude::*, JsCast};
20use web_sys::*;
21
22pub mod prelude {
23    pub use crate::*;
24}
25
26pub fn get_canvas_by_id(id: &str) -> HtmlCanvasElement {
27    let document = window().document().expect("Could not get window document");
28    let canvas = document
29        .get_element_by_id(id)
30        .unwrap_or_else(|| panic!("no `{}` canvas in document", id));
31    canvas
32        .dyn_into::<HtmlCanvasElement>()
33        .map_err(|_| ())
34        .unwrap()
35}
36
37fn window() -> web_sys::Window {
38    web_sys::window().expect("no global `window` exists")
39}
40
41pub struct WebCompositeRenderer {
42    state: RenderState,
43    view_size: Vec2,
44    canvas: HtmlCanvasElement,
45    context: CanvasRenderingContext2d,
46    images_cache: HashMap<String, HtmlCanvasElement>,
47    images_table: HashMap<AssetId, String>,
48    fontfaces_cache: HashMap<String, FontFace>,
49    fontfaces_table: HashMap<AssetId, String>,
50    font_family_map: HashMap<String, String>,
51    cached_image_smoothing: Option<bool>,
52    surfaces_cache: HashMap<String, (HtmlCanvasElement, CanvasRenderingContext2d)>,
53}
54
55unsafe impl Send for WebCompositeRenderer {}
56unsafe impl Sync for WebCompositeRenderer {}
57
58impl WebCompositeRenderer {
59    pub fn new(canvas: HtmlCanvasElement) -> Self {
60        let context = canvas
61            .get_context("2d")
62            .unwrap()
63            .unwrap()
64            .dyn_into::<CanvasRenderingContext2d>()
65            .unwrap();
66        Self {
67            state: RenderState::default(),
68            view_size: Vec2::zero(),
69            canvas,
70            context,
71            images_cache: Default::default(),
72            images_table: Default::default(),
73            fontfaces_cache: Default::default(),
74            fontfaces_table: Default::default(),
75            font_family_map: Default::default(),
76            cached_image_smoothing: None,
77            surfaces_cache: Default::default(),
78        }
79    }
80
81    pub fn with_state(canvas: HtmlCanvasElement, state: RenderState) -> Self {
82        let mut result = Self::new(canvas);
83        *result.state_mut() = state;
84        result
85    }
86
87    #[allow(clippy::many_single_char_names)]
88    fn execute_with<'a, I>(
89        &self,
90        context: &CanvasRenderingContext2d,
91        mut current_alpha: Scalar,
92        commands: I,
93    ) -> Result<(usize, usize)>
94    where
95        I: IntoIterator<Item = Command<'a>>,
96    {
97        let mut render_ops = 0;
98        let mut renderables = 0;
99        let mut alpha_stack = vec![current_alpha];
100        for command in commands {
101            match command {
102                Command::Draw(renderable) => match renderable {
103                    Renderable::None => {}
104                    Renderable::Rectangle(rectangle) => {
105                        context.set_fill_style(&rectangle.color.to_string().into());
106                        context.fill_rect(
107                            rectangle.rect.x.into(),
108                            rectangle.rect.y.into(),
109                            rectangle.rect.w.into(),
110                            rectangle.rect.h.into(),
111                        );
112                        render_ops += 2;
113                        renderables += 1;
114                    }
115                    Renderable::FullscreenRectangle(color) => {
116                        context.save();
117                        drop(context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0));
118                        context.set_fill_style(&color.to_string().into());
119                        context.fill_rect(
120                            0.0,
121                            0.0,
122                            self.view_size.x.into(),
123                            self.view_size.y.into(),
124                        );
125                        context.restore();
126                        render_ops += 2;
127                        renderables += 1;
128                    }
129                    Renderable::Text(text) => {
130                        let name = if let Some(name) = self.font_family_map.get(text.font.as_ref())
131                        {
132                            name
133                        } else {
134                            text.font.as_ref()
135                        };
136                        context.set_fill_style(&text.color.to_string().into());
137                        context.set_font(&format!("{}px \"{}\"", text.size, name));
138                        context.set_text_align(match text.align {
139                            TextAlign::Left => "left",
140                            TextAlign::Center => "center",
141                            TextAlign::Right => "right",
142                        });
143                        context.set_text_baseline(match text.baseline {
144                            TextBaseLine::Top => "top",
145                            TextBaseLine::Middle => "middle",
146                            TextBaseLine::Bottom => "bottom",
147                            TextBaseLine::Alphabetic => "alphabetic",
148                            TextBaseLine::Hanging => "hanging",
149                        });
150                        for (i, line) in text.text.lines().enumerate() {
151                            if let Some(max_width) = text.max_width {
152                                drop(context.fill_text_with_max_width(
153                                    line,
154                                    text.position.x.into(),
155                                    (text.position.y + text.size * i as Scalar).into(),
156                                    max_width.into(),
157                                ));
158                            } else {
159                                drop(context.fill_text(
160                                    line,
161                                    text.position.x.into(),
162                                    (text.position.y + text.size * i as Scalar).into(),
163                                ));
164                            }
165                            render_ops += 1;
166                        }
167                        render_ops += 3;
168                        renderables += 1;
169                    }
170                    Renderable::Path(path) => {
171                        let mut ops = 0;
172                        context.begin_path();
173                        for element in &path.elements {
174                            match element {
175                                PathElement::MoveTo(pos) => {
176                                    context.move_to(pos.x.into(), pos.y.into());
177                                    ops += 1;
178                                }
179                                PathElement::LineTo(pos) => {
180                                    context.line_to(pos.x.into(), pos.y.into());
181                                    ops += 1;
182                                }
183                                PathElement::BezierCurveTo(cpa, cpb, pos) => {
184                                    context.bezier_curve_to(
185                                        cpa.x.into(),
186                                        cpa.y.into(),
187                                        cpb.x.into(),
188                                        cpb.y.into(),
189                                        pos.x.into(),
190                                        pos.y.into(),
191                                    );
192                                    ops += 1;
193                                }
194                                PathElement::QuadraticCurveTo(cp, pos) => {
195                                    context.quadratic_curve_to(
196                                        cp.x.into(),
197                                        cp.y.into(),
198                                        pos.x.into(),
199                                        pos.y.into(),
200                                    );
201                                    ops += 1;
202                                }
203                                PathElement::Arc(pos, r, a) => {
204                                    drop(context.arc(
205                                        pos.x.into(),
206                                        pos.y.into(),
207                                        (*r).into(),
208                                        a.start.into(),
209                                        a.end.into(),
210                                    ));
211                                    ops += 1;
212                                }
213                                PathElement::Ellipse(pos, r, rot, a) => {
214                                    drop(context.ellipse(
215                                        pos.x.into(),
216                                        pos.y.into(),
217                                        r.x.into(),
218                                        r.y.into(),
219                                        (*rot).into(),
220                                        a.start.into(),
221                                        a.end.into(),
222                                    ));
223                                    ops += 1;
224                                }
225                                PathElement::Rectangle(rect) => {
226                                    context.rect(
227                                        rect.x.into(),
228                                        rect.y.into(),
229                                        rect.w.into(),
230                                        rect.h.into(),
231                                    );
232                                    ops += 1;
233                                }
234                            }
235                        }
236                        context.set_fill_style(&path.color.to_string().into());
237                        context.close_path();
238                        context.fill();
239                        render_ops += 4 + ops;
240                        renderables += 1;
241                    }
242                    Renderable::Mask(mask) => {
243                        let mut ops = 0;
244                        context.begin_path();
245                        for element in &mask.elements {
246                            match element {
247                                PathElement::MoveTo(pos) => {
248                                    context.move_to(pos.x.into(), pos.y.into());
249                                    ops += 1;
250                                }
251                                PathElement::LineTo(pos) => {
252                                    context.line_to(pos.x.into(), pos.y.into());
253                                    ops += 1;
254                                }
255                                PathElement::BezierCurveTo(cpa, cpb, pos) => {
256                                    context.bezier_curve_to(
257                                        cpa.x.into(),
258                                        cpa.y.into(),
259                                        cpb.x.into(),
260                                        cpb.y.into(),
261                                        pos.x.into(),
262                                        pos.y.into(),
263                                    );
264                                    ops += 1;
265                                }
266                                PathElement::QuadraticCurveTo(cp, pos) => {
267                                    context.quadratic_curve_to(
268                                        cp.x.into(),
269                                        cp.y.into(),
270                                        pos.x.into(),
271                                        pos.y.into(),
272                                    );
273                                    ops += 1;
274                                }
275                                PathElement::Arc(pos, r, a) => {
276                                    drop(context.arc(
277                                        pos.x.into(),
278                                        pos.y.into(),
279                                        (*r).into(),
280                                        a.start.into(),
281                                        a.end.into(),
282                                    ));
283                                    ops += 1;
284                                }
285                                PathElement::Ellipse(pos, r, rot, a) => {
286                                    drop(context.ellipse(
287                                        pos.x.into(),
288                                        pos.y.into(),
289                                        r.x.into(),
290                                        r.y.into(),
291                                        (*rot).into(),
292                                        a.start.into(),
293                                        a.end.into(),
294                                    ));
295                                    ops += 1;
296                                }
297                                PathElement::Rectangle(rect) => {
298                                    context.rect(
299                                        rect.x.into(),
300                                        rect.y.into(),
301                                        rect.w.into(),
302                                        rect.h.into(),
303                                    );
304                                    ops += 1;
305                                }
306                            }
307                        }
308                        context.close_path();
309                        context.clip();
310                        render_ops += 3 + ops;
311                    }
312                    Renderable::Image(image) => {
313                        let path = image.image.as_ref();
314                        if let Some(elm) = self.images_cache.get(path) {
315                            let mut src = if let Some(src) = image.source {
316                                src
317                            } else {
318                                Rect {
319                                    x: 0.0,
320                                    y: 0.0,
321                                    w: elm.width() as Scalar,
322                                    h: elm.height() as Scalar,
323                                }
324                            };
325                            let dst = if let Some(dst) = image.destination {
326                                dst
327                            } else {
328                                Rect {
329                                    x: 0.0,
330                                    y: 0.0,
331                                    w: src.w,
332                                    h: src.h,
333                                }
334                            }
335                            .align(image.alignment);
336                            src.x += self.state.image_source_inner_margin;
337                            src.y += self.state.image_source_inner_margin;
338                            src.w -= self.state.image_source_inner_margin * 2.0;
339                            src.h -= self.state.image_source_inner_margin * 2.0;
340                            if src.x < 0.0 {
341                                src.w += src.x;
342                                src.x = 0.0;
343                            }
344                            if src.y < 0.0 {
345                                src.h += src.y;
346                                src.y = 0.0;
347                            }
348                            src.w = src.w.max(1.0);
349                            src.h = src.h.max(1.0);
350                            drop(context
351                                .draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
352                                    elm,
353                                    src.x.into(),
354                                    src.y.into(),
355                                    src.w.into(),
356                                    src.h.into(),
357                                    dst.x.into(),
358                                    dst.y.into(),
359                                    dst.w.into(),
360                                    dst.h.into(),
361                                ));
362                            render_ops += 1;
363                            renderables += 1;
364                        } else if let Some((elm, _)) = self.surfaces_cache.get(path) {
365                            let mut src = if let Some(src) = image.source {
366                                src
367                            } else {
368                                Rect {
369                                    x: 0.0,
370                                    y: 0.0,
371                                    w: elm.width() as Scalar,
372                                    h: elm.height() as Scalar,
373                                }
374                            };
375                            let dst = if let Some(dst) = image.destination {
376                                dst
377                            } else {
378                                Rect {
379                                    x: 0.0,
380                                    y: 0.0,
381                                    w: src.w,
382                                    h: src.h,
383                                }
384                            }
385                            .align(image.alignment);
386                            src.x += self.state.image_source_inner_margin;
387                            src.y += self.state.image_source_inner_margin;
388                            src.w -= self.state.image_source_inner_margin * 2.0;
389                            src.h -= self.state.image_source_inner_margin * 2.0;
390                            if src.x < 0.0 {
391                                src.w += src.x;
392                                src.x = 0.0;
393                            }
394                            if src.y < 0.0 {
395                                src.h += src.y;
396                                src.y = 0.0;
397                            }
398                            src.w = src.w.max(1.0);
399                            src.h = src.h.max(1.0);
400                            drop(context
401                                .draw_image_with_html_canvas_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(
402                                    elm,
403                                    src.x.into(),
404                                    src.y.into(),
405                                    src.w.into(),
406                                    src.h.into(),
407                                    dst.x.into(),
408                                    dst.y.into(),
409                                    dst.w.into(),
410                                    dst.h.into(),
411                                ));
412                            render_ops += 1;
413                            renderables += 1;
414                        }
415                    }
416                    Renderable::Triangles(triangles) => {
417                        let path: &str = &triangles.image;
418                        if let Some(elm) = self.images_cache.get(path) {
419                            let w = elm.width() as Scalar;
420                            let h = elm.height() as Scalar;
421                            let s = Vec2::new(w, h);
422                            for triangle in &triangles.faces {
423                                let a = triangles.vertices[triangle.a];
424                                let b = triangles.vertices[triangle.b];
425                                let c = triangles.vertices[triangle.c];
426                                let nab = (b.0 - a.0).normalized();
427                                let nbc = (c.0 - b.0).normalized();
428                                let nca = (a.0 - c.0).normalized();
429                                if !Vec2::is_clockwise(nab, nbc) {
430                                    continue;
431                                }
432                                let pa = a.0 - nbc.right() * self.state.triangles_outer_margin;
433                                let pb = b.0 - nca.right() * self.state.triangles_outer_margin;
434                                let pc = c.0 - nab.right() * self.state.triangles_outer_margin;
435                                context.save();
436                                context.begin_path();
437                                context.move_to(pa.x.into(), pa.y.into());
438                                context.line_to(pb.x.into(), pb.y.into());
439                                context.line_to(pc.x.into(), pc.y.into());
440                                context.close_path();
441                                context.clip();
442                                let s0 = a.1 * s;
443                                let s1 = b.1 * s;
444                                let s2 = c.1 * s;
445                                let denom = s0.x * (s2.y - s1.y) - s1.x * s2.y
446                                    + s2.x * s1.y
447                                    + (s1.x - s2.x) * s0.y;
448                                if denom.abs() > 0.0 {
449                                    let m11 = -(s0.y * (c.0.x - b.0.x) - s1.y * c.0.x
450                                        + s2.y * b.0.x
451                                        + (s1.y - s2.y) * a.0.x)
452                                        / denom;
453                                    let m12 = (s1.y * c.0.y + s0.y * (b.0.y - c.0.y)
454                                        - s2.y * b.0.y
455                                        + (s2.y - s1.y) * a.0.y)
456                                        / denom;
457                                    let m21 = (s0.x * (c.0.x - b.0.x) - s1.x * c.0.x
458                                        + s2.x * b.0.x
459                                        + (s1.x - s2.x) * a.0.x)
460                                        / denom;
461                                    let m22 = -(s1.x * c.0.y + s0.x * (b.0.y - c.0.y)
462                                        - s2.x * b.0.y
463                                        + (s2.x - s1.x) * a.0.y)
464                                        / denom;
465                                    let dx = (s0.x * (s2.y * b.0.x - s1.y * c.0.x)
466                                        + s0.y * (s1.x * c.0.x - s2.x * b.0.x)
467                                        + (s2.x * s1.y - s1.x * s2.y) * a.0.x)
468                                        / denom;
469                                    let dy = (s0.x * (s2.y * b.0.y - s1.y * c.0.y)
470                                        + s0.y * (s1.x * c.0.y - s2.x * b.0.y)
471                                        + (s2.x * s1.y - s1.x * s2.y) * a.0.y)
472                                        / denom;
473                                    drop(context.transform(
474                                        m11.into(),
475                                        m12.into(),
476                                        m21.into(),
477                                        m22.into(),
478                                        dx.into(),
479                                        dy.into(),
480                                    ));
481                                    drop(
482                                        context.draw_image_with_html_canvas_element(elm, 0.0, 0.0),
483                                    );
484                                    render_ops += 2;
485                                    renderables += 1;
486                                }
487                                context.restore();
488                                render_ops += 8;
489                            }
490                        } else if let Some((elm, _)) = self.surfaces_cache.get(path) {
491                            let w = elm.width() as Scalar;
492                            let h = elm.height() as Scalar;
493                            let s = Vec2::new(w, h);
494                            for triangle in &triangles.faces {
495                                let a = triangles.vertices[triangle.a];
496                                let b = triangles.vertices[triangle.b];
497                                let c = triangles.vertices[triangle.c];
498                                let nab = (b.0 - a.0).normalized();
499                                let nbc = (c.0 - b.0).normalized();
500                                let nca = (a.0 - c.0).normalized();
501                                if !Vec2::is_clockwise(nab, nbc) {
502                                    continue;
503                                }
504                                let pa = a.0 - nbc.right() * self.state.triangles_outer_margin;
505                                let pb = b.0 - nca.right() * self.state.triangles_outer_margin;
506                                let pc = c.0 - nab.right() * self.state.triangles_outer_margin;
507                                context.save();
508                                context.begin_path();
509                                context.move_to(pa.x.into(), pa.y.into());
510                                context.line_to(pb.x.into(), pb.y.into());
511                                context.line_to(pc.x.into(), pc.y.into());
512                                context.close_path();
513                                context.clip();
514                                let s0 = a.1 * s;
515                                let s1 = b.1 * s;
516                                let s2 = c.1 * s;
517                                let denom = s0.x * (s2.y - s1.y) - s1.x * s2.y
518                                    + s2.x * s1.y
519                                    + (s1.x - s2.x) * s0.y;
520                                if denom.abs() > 0.0 {
521                                    let m11 = -(s0.y * (c.0.x - b.0.x) - s1.y * c.0.x
522                                        + s2.y * b.0.x
523                                        + (s1.y - s2.y) * a.0.x)
524                                        / denom;
525                                    let m12 = (s1.y * c.0.y + s0.y * (b.0.y - c.0.y)
526                                        - s2.y * b.0.y
527                                        + (s2.y - s1.y) * a.0.y)
528                                        / denom;
529                                    let m21 = (s0.x * (c.0.x - b.0.x) - s1.x * c.0.x
530                                        + s2.x * b.0.x
531                                        + (s1.x - s2.x) * a.0.x)
532                                        / denom;
533                                    let m22 = -(s1.x * c.0.y + s0.x * (b.0.y - c.0.y)
534                                        - s2.x * b.0.y
535                                        + (s2.x - s1.x) * a.0.y)
536                                        / denom;
537                                    let dx = (s0.x * (s2.y * b.0.x - s1.y * c.0.x)
538                                        + s0.y * (s1.x * c.0.x - s2.x * b.0.x)
539                                        + (s2.x * s1.y - s1.x * s2.y) * a.0.x)
540                                        / denom;
541                                    let dy = (s0.x * (s2.y * b.0.y - s1.y * c.0.y)
542                                        + s0.y * (s1.x * c.0.y - s2.x * b.0.y)
543                                        + (s2.x * s1.y - s1.x * s2.y) * a.0.y)
544                                        / denom;
545                                    drop(context.transform(
546                                        m11.into(),
547                                        m12.into(),
548                                        m21.into(),
549                                        m22.into(),
550                                        dx.into(),
551                                        dy.into(),
552                                    ));
553                                    drop(
554                                        context.draw_image_with_html_canvas_element(elm, 0.0, 0.0),
555                                    );
556                                    render_ops += 2;
557                                    renderables += 1;
558                                }
559                                context.restore();
560                                render_ops += 8;
561                            }
562                        }
563                    }
564                    Renderable::Commands(commands) => {
565                        let (o, r) =
566                            self.execute_with(context, current_alpha, commands.into_iter())?;
567                        render_ops += o;
568                        renderables += r;
569                    }
570                },
571                Command::Stroke(line_width, renderable) => match renderable {
572                    Renderable::None => {}
573                    Renderable::Rectangle(rectangle) => {
574                        context.set_stroke_style(&rectangle.color.to_string().into());
575                        context.set_line_width(line_width.into());
576                        context.stroke_rect(
577                            rectangle.rect.x.into(),
578                            rectangle.rect.y.into(),
579                            rectangle.rect.w.into(),
580                            rectangle.rect.h.into(),
581                        );
582                        render_ops += 3;
583                        renderables += 1;
584                    }
585                    Renderable::FullscreenRectangle(color) => {
586                        context.save();
587                        drop(context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0));
588                        context.set_stroke_style(&color.to_string().into());
589                        context.set_line_width(line_width.into());
590                        context.fill_rect(
591                            0.0,
592                            0.0,
593                            self.view_size.x.into(),
594                            self.view_size.y.into(),
595                        );
596                        context.restore();
597                        render_ops += 2;
598                        renderables += 1;
599                    }
600                    Renderable::Text(text) => {
601                        let name = if let Some(name) = self.font_family_map.get(text.font.as_ref())
602                        {
603                            name
604                        } else {
605                            text.font.as_ref()
606                        };
607                        context.set_stroke_style(&text.color.to_string().into());
608                        context.set_line_width(line_width.into());
609                        context.set_font(&format!("{}px \"{}\"", text.size, name));
610                        context.set_text_align(match text.align {
611                            TextAlign::Left => "left",
612                            TextAlign::Center => "center",
613                            TextAlign::Right => "right",
614                        });
615                        context.set_text_baseline(match text.baseline {
616                            TextBaseLine::Top => "top",
617                            TextBaseLine::Middle => "middle",
618                            TextBaseLine::Bottom => "bottom",
619                            TextBaseLine::Alphabetic => "alphabetic",
620                            TextBaseLine::Hanging => "hanging",
621                        });
622                        for (i, line) in text.text.lines().enumerate() {
623                            if let Some(max_width) = text.max_width {
624                                drop(context.stroke_text_with_max_width(
625                                    line,
626                                    text.position.x.into(),
627                                    (text.position.y + text.size * i as Scalar).into(),
628                                    max_width.into(),
629                                ));
630                            } else {
631                                drop(context.stroke_text(
632                                    line,
633                                    text.position.x.into(),
634                                    (text.position.y + text.size * i as Scalar).into(),
635                                ));
636                            }
637                            render_ops += 1;
638                        }
639                        render_ops += 4;
640                        renderables += 1;
641                    }
642                    Renderable::Path(path) => {
643                        let mut ops = 0;
644                        context.begin_path();
645                        for element in &path.elements {
646                            match element {
647                                PathElement::MoveTo(pos) => {
648                                    context.move_to(pos.x.into(), pos.y.into());
649                                    ops += 1;
650                                }
651                                PathElement::LineTo(pos) => {
652                                    context.line_to(pos.x.into(), pos.y.into());
653                                    ops += 1;
654                                }
655                                PathElement::BezierCurveTo(cpa, cpb, pos) => {
656                                    context.bezier_curve_to(
657                                        cpa.x.into(),
658                                        cpa.y.into(),
659                                        cpb.x.into(),
660                                        cpb.y.into(),
661                                        pos.x.into(),
662                                        pos.y.into(),
663                                    );
664                                    ops += 1;
665                                }
666                                PathElement::QuadraticCurveTo(cp, pos) => {
667                                    context.quadratic_curve_to(
668                                        cp.x.into(),
669                                        cp.y.into(),
670                                        pos.x.into(),
671                                        pos.y.into(),
672                                    );
673                                    ops += 1;
674                                }
675                                PathElement::Arc(pos, r, a) => {
676                                    drop(context.arc(
677                                        pos.x.into(),
678                                        pos.y.into(),
679                                        (*r).into(),
680                                        a.start.into(),
681                                        a.end.into(),
682                                    ));
683                                    ops += 1;
684                                }
685                                PathElement::Ellipse(pos, r, rot, a) => {
686                                    drop(context.ellipse(
687                                        pos.x.into(),
688                                        pos.y.into(),
689                                        r.x.into(),
690                                        r.y.into(),
691                                        (*rot).into(),
692                                        a.start.into(),
693                                        a.end.into(),
694                                    ));
695                                    ops += 1;
696                                }
697                                PathElement::Rectangle(rect) => {
698                                    context.rect(
699                                        rect.x.into(),
700                                        rect.y.into(),
701                                        rect.w.into(),
702                                        rect.h.into(),
703                                    );
704                                    ops += 1;
705                                }
706                            }
707                        }
708                        context.set_stroke_style(&path.color.to_string().into());
709                        context.set_line_width(line_width.into());
710                        context.close_path();
711                        context.stroke();
712                        render_ops += 5 + ops;
713                        renderables += 1;
714                    }
715                    Renderable::Mask(_) => error!("Trying to make stroked mask"),
716                    Renderable::Image(image) => {
717                        error!("Trying to render stroked image: {}", image.image)
718                    }
719                    Renderable::Triangles(triangles) => {
720                        context.save();
721                        context.set_stroke_style(&triangles.color.to_string().into());
722                        context.set_line_width(line_width.into());
723                        for triangle in &triangles.faces {
724                            let a = triangles.vertices[triangle.a];
725                            let b = triangles.vertices[triangle.b];
726                            let c = triangles.vertices[triangle.c];
727                            context.begin_path();
728                            context.move_to(a.0.x.into(), a.0.y.into());
729                            context.line_to(b.0.x.into(), b.0.y.into());
730                            context.line_to(c.0.x.into(), c.0.y.into());
731                            context.close_path();
732                            context.stroke();
733                            render_ops += 6;
734                            renderables += 1;
735                        }
736                        context.restore();
737                        render_ops += 4;
738                    }
739                    Renderable::Commands(commands) => {
740                        error!("Trying to render stroked subcommands: {:#?}", commands)
741                    }
742                },
743                Command::Transform(a, b, c, d, e, f) => {
744                    drop(context.transform(
745                        a.into(),
746                        b.into(),
747                        c.into(),
748                        d.into(),
749                        e.into(),
750                        f.into(),
751                    ));
752                    render_ops += 1;
753                }
754                Command::Effect(effect) => {
755                    drop(context.set_global_composite_operation(&effect.to_string()));
756                    render_ops += 1;
757                }
758                Command::Alpha(alpha) => {
759                    current_alpha = alpha_stack.last().copied().unwrap_or(1.0) * alpha;
760                    context.set_global_alpha(current_alpha.max(0.0).min(1.0).into());
761                    render_ops += 1;
762                }
763                Command::Filter(data) => {
764                    context.set_filter(data.as_ref());
765                    render_ops += 1;
766                }
767                Command::Smoothing(value) => {
768                    context.set_image_smoothing_enabled(value);
769                    render_ops += 1;
770                }
771                Command::Store => {
772                    alpha_stack.push(current_alpha);
773                    context.save();
774                    render_ops += 1;
775                }
776                Command::Restore => {
777                    current_alpha = alpha_stack.pop().unwrap_or(1.0);
778                    context.restore();
779                    render_ops += 1;
780                }
781                Command::None => {}
782            }
783        }
784        Ok((render_ops, renderables))
785    }
786}
787
788impl CompositeRenderer for WebCompositeRenderer {
789    fn execute<'a, I>(&mut self, commands: I) -> Result<(usize, usize)>
790    where
791        I: IntoIterator<Item = Command<'a>>,
792    {
793        self.execute_with(&self.context, 1.0, commands)
794    }
795
796    fn images_count(&self) -> usize {
797        self.images_cache.len()
798    }
799
800    fn fontfaces_count(&self) -> usize {
801        self.fontfaces_cache.len()
802    }
803
804    fn surfaces_count(&self) -> usize {
805        self.surfaces_cache.len()
806    }
807
808    fn state(&self) -> &RenderState {
809        &self.state
810    }
811
812    fn state_mut(&mut self) -> &mut RenderState {
813        &mut self.state
814    }
815
816    fn view_size(&self) -> Vec2 {
817        self.view_size
818    }
819
820    fn update_state(&mut self) {
821        let w = self.canvas.client_width();
822        let h = self.canvas.client_height();
823        if (self.view_size.x - w as Scalar).abs() > 1.0
824            || (self.view_size.y - h as Scalar).abs() > 1.0
825        {
826            self.canvas.set_width(w as u32);
827            self.canvas.set_height(h as u32);
828            self.view_size = Vec2::new(w as Scalar, h as Scalar);
829            self.cached_image_smoothing = None;
830        }
831        if self.cached_image_smoothing.is_none()
832            || self.cached_image_smoothing.unwrap() != self.state.image_smoothing
833        {
834            self.context
835                .set_image_smoothing_enabled(self.state.image_smoothing);
836            self.cached_image_smoothing = Some(self.state.image_smoothing);
837        }
838    }
839
840    fn update_cache(&mut self, assets: &AssetsDatabase) {
841        for id in assets.lately_loaded_protocol("png") {
842            let id = *id;
843            let asset = assets
844                .asset_by_id(id)
845                .expect("trying to use not loaded png asset");
846            let path = asset.path().to_owned();
847            let asset = asset
848                .get::<PngImageAsset>()
849                .expect("trying to use non-png asset");
850            let width = asset.width() as u32;
851            let height = asset.height() as u32;
852            let buffer = Uint8Array::from(asset.bytes());
853            let buffer_val: &JsValue = buffer.as_ref();
854            let parts = Array::new_with_length(1);
855            parts.set(0, buffer_val.clone());
856            let mut options = BlobPropertyBag::new();
857            options.type_("image/png");
858            let blob =
859                Blob::new_with_u8_array_sequence_and_options(parts.as_ref(), &options).unwrap();
860            let elm = HtmlImageElement::new_with_width_and_height(width, height).unwrap();
861            elm.set_src(&Url::create_object_url_with_blob(&blob).unwrap());
862            let document = window().document().expect("Could not get window document");
863            let canvas = document
864                .create_element("canvas")
865                .expect("Could not create canvas element")
866                .dyn_into::<HtmlCanvasElement>()
867                .unwrap();
868            canvas.set_width(width);
869            canvas.set_height(height);
870            let context = canvas
871                .get_context("2d")
872                .unwrap()
873                .unwrap()
874                .dyn_into::<CanvasRenderingContext2d>()
875                .unwrap();
876            let elm2 = elm.clone();
877            let rc = Rc::new(RefCell::new(WebClosure::default()));
878            let rc2 = Rc::clone(&rc);
879            let closure = Closure::wrap(Box::new(move |_: web_sys::Event| {
880                drop(context.draw_image_with_html_image_element(&elm2, 0.0, 0.0));
881                rc2.borrow_mut().release();
882            }) as Box<dyn FnMut(_)>);
883            elm.set_onload(Some(closure.as_ref().unchecked_ref()));
884            *rc.borrow_mut() = WebClosure::acquire(closure);
885            self.images_cache.insert(path.clone(), canvas);
886            self.images_table.insert(id, path);
887        }
888        for id in assets.lately_unloaded_protocol("png") {
889            if let Some(path) = self.images_table.remove(id) {
890                self.images_cache.remove(&path);
891            }
892        }
893        for id in assets.lately_loaded_protocol("jpg") {
894            let id = *id;
895            let asset = assets
896                .asset_by_id(id)
897                .expect("trying to use not loaded jpg asset");
898            let path = asset.path().to_owned();
899            let asset = asset
900                .get::<JpgImageAsset>()
901                .expect("trying to use non-jpg asset");
902            let width = asset.width() as u32;
903            let height = asset.height() as u32;
904            let buffer = Uint8Array::from(asset.bytes());
905            let buffer_val: &JsValue = buffer.as_ref();
906            let parts = Array::new_with_length(1);
907            parts.set(0, buffer_val.clone());
908            let mut options = BlobPropertyBag::new();
909            options.type_("image/jpeg");
910            let blob =
911                Blob::new_with_u8_array_sequence_and_options(parts.as_ref(), &options).unwrap();
912            let elm = HtmlImageElement::new_with_width_and_height(width, height).unwrap();
913            elm.set_src(&Url::create_object_url_with_blob(&blob).unwrap());
914            let document = window().document().expect("Could not get window document");
915            let canvas = document
916                .create_element("canvas")
917                .expect("Could not create canvas element")
918                .dyn_into::<HtmlCanvasElement>()
919                .unwrap();
920            canvas.set_width(width);
921            canvas.set_height(height);
922            let context = canvas
923                .get_context("2d")
924                .unwrap()
925                .unwrap()
926                .dyn_into::<CanvasRenderingContext2d>()
927                .unwrap();
928            let elm2 = elm.clone();
929            let rc = Rc::new(RefCell::new(WebClosure::default()));
930            let rc2 = Rc::clone(&rc);
931            let closure = Closure::wrap(Box::new(move |_: web_sys::Event| {
932                drop(context.draw_image_with_html_image_element(&elm2, 0.0, 0.0));
933                rc2.borrow_mut().release();
934            }) as Box<dyn FnMut(_)>);
935            elm.set_onload(Some(closure.as_ref().unchecked_ref()));
936            *rc.borrow_mut() = WebClosure::acquire(closure);
937            self.images_cache.insert(path.clone(), canvas);
938            self.images_table.insert(id, path);
939        }
940        for id in assets.lately_unloaded_protocol("jpg") {
941            if let Some(path) = self.images_table.remove(id) {
942                self.images_cache.remove(&path);
943            }
944        }
945        for id in assets.lately_loaded_protocol("svg") {
946            let id = *id;
947            let asset = assets
948                .asset_by_id(id)
949                .expect("trying to use not loaded svg asset");
950            let path = asset.path().to_owned();
951            let asset = asset
952                .get::<SvgImageAsset>()
953                .expect("trying to use non-svg asset");
954            let width = asset.width() as u32;
955            let height = asset.height() as u32;
956            let buffer = Uint8Array::from(asset.bytes());
957            let buffer_val: &JsValue = buffer.as_ref();
958            let parts = Array::new_with_length(1);
959            parts.set(0, buffer_val.clone());
960            let mut options = BlobPropertyBag::new();
961            options.type_("image/svg+xml;charset=utf-8");
962            let blob =
963                Blob::new_with_u8_array_sequence_and_options(parts.as_ref(), &options).unwrap();
964            let elm = HtmlImageElement::new_with_width_and_height(width, height).unwrap();
965            elm.set_src(&Url::create_object_url_with_blob(&blob).unwrap());
966            let document = window().document().expect("Could not get window document");
967            let canvas = document
968                .create_element("canvas")
969                .expect("Could not create canvas element")
970                .dyn_into::<HtmlCanvasElement>()
971                .unwrap();
972            canvas.set_width(width);
973            canvas.set_height(height);
974            let context = canvas
975                .get_context("2d")
976                .unwrap()
977                .unwrap()
978                .dyn_into::<CanvasRenderingContext2d>()
979                .unwrap();
980            let elm2 = elm.clone();
981            let rc = Rc::new(RefCell::new(WebClosure::default()));
982            let rc2 = Rc::clone(&rc);
983            let closure = Closure::wrap(Box::new(move |_: web_sys::Event| {
984                drop(context.draw_image_with_html_image_element(&elm2, 0.0, 0.0));
985                rc2.borrow_mut().release();
986            }) as Box<dyn FnMut(_)>);
987            elm.set_onload(Some(closure.as_ref().unchecked_ref()));
988            *rc.borrow_mut() = WebClosure::acquire(closure);
989            self.images_cache.insert(path.clone(), canvas);
990            self.images_table.insert(id, path);
991        }
992        for id in assets.lately_unloaded_protocol("svg") {
993            if let Some(path) = self.images_table.remove(id) {
994                self.images_cache.remove(&path);
995            }
996        }
997        for id in assets.lately_loaded_protocol("fontface") {
998            let id = *id;
999            let asset = assets
1000                .asset_by_id(id)
1001                .expect("trying to use not loaded font face asset");
1002            let path = asset.path().to_owned();
1003            let asset = asset
1004                .get::<FontFaceAsset>()
1005                .expect("trying to use non-font-face asset");
1006            let font_asset = assets
1007                .asset_by_id(asset.font_asset())
1008                .expect("trying to use not loaded font asset");
1009            let font_asset = font_asset
1010                .get::<FontAsset>()
1011                .expect("trying to use non-font asset");
1012            let mut descriptors = FontFaceDescriptors::new();
1013            if let Some(style) = &asset.face().style {
1014                descriptors.style(style);
1015            }
1016            descriptors.weight(&asset.face().weight.to_string());
1017            descriptors.stretch(&format!("{}%", asset.face().stretch));
1018            if let Some(variant) = &asset.face().variant {
1019                descriptors.variant(variant);
1020            }
1021            let name = path.replace(|c: char| !c.is_alphanumeric(), "-");
1022            let elm = FontFace::new_with_u8_array_and_descriptors(
1023                &name,
1024                #[allow(mutable_transmutes, clippy::transmute_ptr_to_ptr)]
1025                unsafe {
1026                    std::mem::transmute(font_asset.bytes())
1027                },
1028                &descriptors,
1029            )
1030            .unwrap();
1031            drop(
1032                elm.load()
1033                    .unwrap_or_else(|_| panic!("Could not load font: {}", path)),
1034            );
1035            window()
1036                .document()
1037                .expect("Could not get window document")
1038                .fonts()
1039                .add(&elm)
1040                .unwrap_or_else(|_| panic!("Could not add font: {}", path));
1041            self.fontfaces_cache.insert(path.clone(), elm);
1042            self.fontfaces_table.insert(id, path.clone());
1043            self.font_family_map.insert(path, name);
1044        }
1045        for id in assets.lately_unloaded_protocol("fontface") {
1046            if let Some(path) = self.fontfaces_table.remove(id) {
1047                self.fontfaces_cache.remove(&path);
1048                self.font_family_map.remove(&path);
1049            }
1050        }
1051    }
1052
1053    fn create_surface(&mut self, name: &str, width: usize, height: usize) -> bool {
1054        let document = window().document().expect("Could not get window document");
1055        let canvas = document
1056            .create_element("canvas")
1057            .expect("Could not create canvas element")
1058            .dyn_into::<HtmlCanvasElement>()
1059            .unwrap();
1060        canvas.set_width(width as u32);
1061        canvas.set_height(height as u32);
1062        let context = canvas
1063            .get_context("2d")
1064            .unwrap()
1065            .unwrap()
1066            .dyn_into::<CanvasRenderingContext2d>()
1067            .unwrap();
1068        self.surfaces_cache
1069            .insert(name.to_owned(), (canvas, context));
1070        true
1071    }
1072
1073    fn destroy_surface(&mut self, name: &str) -> bool {
1074        if let Some((canvas, _)) = self.surfaces_cache.remove(name) {
1075            canvas.remove();
1076            true
1077        } else {
1078            false
1079        }
1080    }
1081
1082    fn has_surface(&mut self, name: &str) -> bool {
1083        self.surfaces_cache.contains_key(name)
1084    }
1085
1086    fn get_surface_size(&self, name: &str) -> Option<(usize, usize)> {
1087        self.surfaces_cache
1088            .get(name)
1089            .map(|(canvas, _)| (canvas.width() as usize, canvas.height() as usize))
1090    }
1091
1092    fn update_surface<'a, I>(&mut self, name: &str, commands: I) -> Result<(usize, usize)>
1093    where
1094        I: IntoIterator<Item = Command<'a>>,
1095    {
1096        if let Some((canvas, context)) = self.surfaces_cache.get(name) {
1097            context.clear_rect(0.0, 0.0, canvas.width().into(), canvas.height().into());
1098            self.execute_with(context, 1.0, commands)
1099        } else {
1100            Err(Error::Message(format!("There is no surface: {}", name)))
1101        }
1102    }
1103}
1104
1105impl CompositeRendererResources<HtmlCanvasElement> for WebCompositeRenderer {
1106    fn add_resource(&mut self, id: String, resource: HtmlCanvasElement) -> Result<AssetId> {
1107        let asset_id = AssetId::new();
1108        self.images_cache.insert(id.clone(), resource);
1109        self.images_table.insert(asset_id, id);
1110        Ok(asset_id)
1111    }
1112
1113    fn remove_resource(&mut self, id: AssetId) -> Result<HtmlCanvasElement> {
1114        if let Some(id) = self.images_table.remove(&id) {
1115            if let Some(resource) = self.images_cache.remove(&id) {
1116                Ok(resource)
1117            } else {
1118                Err(Error::Message(format!(
1119                    "Image resource does not exists in cache: {:?}",
1120                    id
1121                )))
1122            }
1123        } else {
1124            Err(Error::Message(format!(
1125                "Image resource does not exists in table: {:?}",
1126                id
1127            )))
1128        }
1129    }
1130}
1131
1132impl CompositeRendererResources<FontFace> for WebCompositeRenderer {
1133    fn add_resource(&mut self, id: String, resource: FontFace) -> Result<AssetId> {
1134        let asset_id = AssetId::new();
1135        self.fontfaces_cache.insert(id.clone(), resource);
1136        self.fontfaces_table.insert(asset_id, id);
1137        Ok(asset_id)
1138    }
1139
1140    fn remove_resource(&mut self, id: AssetId) -> Result<FontFace> {
1141        if let Some(id) = self.fontfaces_table.remove(&id) {
1142            if let Some(resource) = self.fontfaces_cache.remove(&id) {
1143                Ok(resource)
1144            } else {
1145                Err(Error::Message(format!(
1146                    "Font face resource does not exists in cache: {:?}",
1147                    id
1148                )))
1149            }
1150        } else {
1151            Err(Error::Message(format!(
1152                "Font face esource does not exists in table: {:?}",
1153                id
1154            )))
1155        }
1156    }
1157}