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}