Skip to main content

reflexo_vec2canvas/
ops.rs

1#![allow(unused)]
2
3use async_trait::async_trait;
4use reflexo_vec2bbox::Vec2BBoxPass;
5
6use crate::{utils::EmptyFuture, CanvasDevice};
7use ecow::EcoVec;
8
9use std::{
10    fmt::Debug,
11    pin::Pin,
12    sync::{
13        atomic::{AtomicBool, Ordering},
14        Arc, Mutex,
15    },
16};
17
18use js_sys::Promise;
19use tiny_skia as sk;
20
21use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
22use web_sys::{CanvasWindingRule, ImageBitmap, OffscreenCanvas, Path2d};
23
24use reflexo::vector::ir::{
25    self, FlatGlyphItem, Image, ImageItem, ImmutStr, PathStyle, Rect, Scalar,
26};
27
28use super::{rasterize_image, set_transform, BBoxAt, CanvasBBox, CanvasStateGuard};
29
30/// A reference to a canvas element.
31pub type CanvasNode = Arc<CanvasElem>;
32/// 2d Context
33type Context2d = web_sys::CanvasRenderingContext2d;
34
35/// The trait for all the operations that can be performed on some canvas
36/// element.
37#[async_trait(?Send)]
38pub trait CanvasOp {
39    /// Prepares the resource (recursively) for the action.
40    fn prepare(
41        &self,
42        ts: sk::Transform,
43    ) -> Option<impl core::future::Future<Output = ()> + Sized + 'static>;
44    /// Realizes the action on the canvas.
45    async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice);
46}
47
48/// A static enum for all the canvas elements.
49#[derive(Debug)]
50pub enum CanvasElem {
51    /// A group of canvas elements.
52    Group(CanvasGroupElem),
53    /// references a canvas element with a clip path.
54    Clip(CanvasClipElem),
55    /// A path element.
56    Path(CanvasPathElem),
57    /// An image element.
58    Image(CanvasImageElem),
59    /// A glyph element.
60    Glyph(CanvasGlyphElem),
61}
62
63#[async_trait(?Send)]
64impl CanvasOp for CanvasElem {
65    fn prepare(
66        &self,
67        ts: sk::Transform,
68    ) -> Option<impl core::future::Future<Output = ()> + Sized + 'static> {
69        type DynFutureBox = Pin<Box<dyn core::future::Future<Output = ()>>>;
70
71        match self {
72            CanvasElem::Group(g) => g.prepare(ts).map(|e| {
73                let e: DynFutureBox = Box::pin(e);
74                e
75            }),
76            CanvasElem::Clip(g) => g.prepare(ts).map(|e| {
77                let e: DynFutureBox = Box::pin(e);
78                e
79            }),
80            CanvasElem::Path(g) => g.prepare(ts).map(|e| {
81                let e: DynFutureBox = Box::pin(e);
82                e
83            }),
84            CanvasElem::Image(g) => g.prepare(ts).map(|e| {
85                let e: DynFutureBox = Box::pin(e);
86                e
87            }),
88            CanvasElem::Glyph(g) => g.prepare(ts).map(|e| {
89                let e: DynFutureBox = Box::pin(e);
90                e
91            }),
92        }
93    }
94
95    async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice) {
96        match self {
97            CanvasElem::Group(g) => g.realize(ts, canvas).await,
98            CanvasElem::Clip(g) => g.realize(ts, canvas).await,
99            CanvasElem::Path(g) => g.realize(ts, canvas).await,
100            CanvasElem::Image(g) => g.realize(ts, canvas).await,
101            CanvasElem::Glyph(g) => g.realize(ts, canvas).await,
102        }
103    }
104}
105
106#[derive(Debug, Clone, Copy)]
107pub enum GroupKind {
108    General,
109    Text,
110}
111
112/// A group of canvas elements.
113#[derive(Debug)]
114pub struct CanvasGroupElem {
115    pub ts: Box<sk::Transform>,
116    pub inner: EcoVec<(ir::Point, CanvasNode)>,
117    pub kind: GroupKind,
118    pub rect: CanvasBBox,
119}
120
121#[async_trait(?Send)]
122impl CanvasOp for CanvasGroupElem {
123    fn prepare(
124        &self,
125        rts: sk::Transform,
126    ) -> Option<impl core::future::Future<Output = ()> + Sized + 'static> {
127        let mut v = Vec::default();
128
129        for (_, sub_elem) in &self.inner {
130            if let Some(f) = sub_elem.prepare(rts) {
131                v.push(f);
132            }
133        }
134
135        if v.is_empty() {
136            None
137        } else {
138            Some(async move {
139                for f in v {
140                    f.await;
141                }
142            })
143        }
144    }
145
146    async fn realize(&self, rts: sk::Transform, canvas: &dyn CanvasDevice) {
147        let ts = rts.pre_concat(*self.ts.as_ref());
148
149        for (pos, sub_elem) in &self.inner {
150            let ts = ts.pre_translate(pos.x.0, pos.y.0);
151            sub_elem.realize(ts, canvas).await;
152        }
153
154        let _ = self.rect;
155        let _ = Self::bbox_at;
156        #[cfg(feature = "report_group")]
157        web_sys::console::log_1(
158            &format!("realize group {:?}({} elems)", self.kind, self.inner.len()).into(),
159        );
160
161        #[cfg(feature = "render_bbox")]
162        {
163            // realize bbox
164            let bbox = self.bbox_at(rts);
165            let color = if matches!(self.kind, GroupKind::Text) {
166                "red"
167            } else {
168                "green"
169            };
170
171            render_bbox(canvas, bbox, color);
172
173            #[cfg(feature = "report_bbox")]
174            web_sys::console::log_1(&format!("realize group bbox {:?} {:?}", ts, bbox).into());
175        }
176    }
177}
178
179/// A reference to a canvas element with a clip path.
180#[derive(Debug)]
181pub struct CanvasClipElem {
182    pub d: ImmutStr,
183    pub inner: CanvasNode,
184    pub clip_bbox: CanvasBBox,
185}
186
187impl CanvasClipElem {
188    pub fn clip_bbox_at(&self, ts: sk::Transform) -> Option<Rect> {
189        self.clip_bbox
190            .bbox_at(ts, || Vec2BBoxPass::simple_path_bbox(&self.d, ts))
191    }
192
193    pub fn realize_with<'a>(
194        &self,
195        ts: sk::Transform,
196        canvas: &'a dyn CanvasDevice,
197    ) -> CanvasStateGuard<'a> {
198        let guard = CanvasStateGuard::new(canvas);
199
200        if !set_transform(canvas, ts) {
201            return guard;
202        }
203        canvas.clip_with_path_2d(&Path2d::new_with_path_string(&self.d).unwrap());
204
205        guard
206    }
207}
208
209#[async_trait(?Send)]
210impl CanvasOp for CanvasClipElem {
211    fn prepare(
212        &self,
213        ts: sk::Transform,
214    ) -> Option<impl core::future::Future<Output = ()> + Sized + 'static> {
215        self.inner.prepare(ts)
216    }
217
218    async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice) {
219        let _guard = self.realize_with(ts, canvas);
220
221        self.inner.realize(ts, canvas).await
222    }
223}
224
225/// A path element.
226#[derive(Debug)]
227pub struct CanvasPathElem {
228    pub path_data: Box<ir::PathItem>,
229    pub rect: CanvasBBox,
230}
231
232#[async_trait(?Send)]
233impl CanvasOp for CanvasPathElem {
234    fn prepare(
235        &self,
236        ts: sk::Transform,
237    ) -> Option<impl core::future::Future<Output = ()> + 'static> {
238        let _ = ts;
239        None::<EmptyFuture>
240    }
241
242    async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice) {
243        let _guard = CanvasStateGuard::new(canvas);
244
245        if !set_transform(canvas, ts) {
246            return;
247        }
248        // map_err(map_err("CanvasRenderTask.BuildPath2d")
249
250        let mut fill_color = "none".into();
251        let mut fill = false;
252        let mut fill_rule = None;
253        let mut stroke_color = "none".into();
254        let mut stroke = false;
255        let mut stroke_width = 0.;
256
257        for style in &self.path_data.styles {
258            match style {
259                PathStyle::Fill(color) => {
260                    fill_color = color.clone();
261                    fill = true;
262                }
263                PathStyle::Stroke(color) => {
264                    stroke_color = color.clone();
265                    stroke = true;
266                }
267                PathStyle::StrokeWidth(width) => {
268                    canvas.set_line_width(width.0 as f64);
269                    stroke_width = width.0;
270                }
271                PathStyle::StrokeLineCap(cap) => {
272                    canvas.set_line_cap(cap);
273                }
274                PathStyle::StrokeLineJoin(join) => {
275                    canvas.set_line_join(join);
276                }
277                PathStyle::StrokeMitterLimit(limit) => {
278                    canvas.set_miter_limit(limit.0 as f64);
279                }
280                PathStyle::StrokeDashArray(array) => {
281                    let dash_array = js_sys::Array::from_iter(
282                        array.iter().map(|d| JsValue::from_f64(d.0 as f64)),
283                    );
284                    canvas.set_line_dash(&dash_array);
285                }
286                PathStyle::StrokeDashOffset(offset) => {
287                    canvas.set_line_dash_offset(offset.0 as f64);
288                }
289                PathStyle::FillRule(rule) => {
290                    fill_rule = match rule.as_ref() {
291                        "nonzero" => Some(CanvasWindingRule::Nonzero),
292                        "evenodd" => Some(CanvasWindingRule::Evenodd),
293                        _ => None,
294                    };
295                }
296            }
297        }
298
299        if fill {
300            // todo: canvas gradient and pattern
301            if fill_color.starts_with('@') {
302                fill_color = "black".into()
303            }
304            canvas.set_fill_style_str(fill_color.as_ref());
305            if let Some(rule) = fill_rule {
306                canvas.fill_with_path_2d_and_winding(
307                    &Path2d::new_with_path_string(&self.path_data.d).unwrap(),
308                    rule,
309                );
310            } else {
311                canvas.fill_with_path_2d(&Path2d::new_with_path_string(&self.path_data.d).unwrap());
312            }
313        }
314
315        if stroke && stroke_width.abs() > 1e-5 {
316            // todo: canvas gradient and pattern
317            if stroke_color.starts_with('@') {
318                stroke_color = "black".into()
319            }
320
321            canvas.set_stroke_style_str(stroke_color.as_ref());
322            canvas.stroke_with_path(&Path2d::new_with_path_string(&self.path_data.d).unwrap());
323        }
324
325        #[cfg(feature = "render_bbox")]
326        {
327            // realize bbox
328            let bbox = self.bbox_at(ts);
329            render_bbox(canvas, bbox, "blue");
330
331            #[cfg(feature = "report_bbox")]
332            web_sys::console::log_1(
333                &format!("bbox_at path {:?} {:?} {:?}", self.path_data, ts, bbox).into(),
334            );
335        }
336    }
337}
338
339/// An image element.
340#[derive(Debug)]
341pub struct CanvasImageElem {
342    pub image_data: ImageItem,
343}
344
345impl CanvasImageElem {
346    fn prepare_image(image: Arc<Image>) -> Option<impl core::future::Future<Output = ()>> {
347        let image_elem = rasterize_image(image.clone()).unwrap().0;
348
349        let loaded = image_elem.loaded.lock().unwrap();
350        if loaded.is_some() {
351            return None;
352        }
353
354        let image = image.clone();
355        Some(async move {
356            wasm_bindgen_futures::JsFuture::from(image_elem.elem)
357                .await
358                .unwrap();
359        })
360    }
361
362    async fn draw_image(ts: sk::Transform, canvas: &dyn CanvasDevice, image_data: &ImageItem) {
363        if !set_transform(canvas, ts) {
364            return;
365        }
366
367        let image = &image_data.image;
368
369        let image_elem = rasterize_image(image.clone()).unwrap().0;
370        let elem = wasm_bindgen_futures::JsFuture::from(image_elem.elem)
371            .await
372            .unwrap();
373
374        // resize image to fit the view
375        let (w, h) = {
376            let size = image_data.size;
377            let view_width = size.x.0;
378            let view_height = size.y.0;
379
380            let aspect = (image.width() as f32) / (image.height() as f32);
381
382            let w: f32 = view_width.max(aspect * view_height);
383            let h: f32 = w / aspect;
384            (w as f64, h as f64)
385        };
386
387        let state = CanvasStateGuard::new(canvas);
388        if !set_transform(canvas, ts) {
389            return;
390        }
391
392        match elem.dyn_into::<ImageBitmap>() {
393            Ok(image_elem) => {
394                canvas.draw_image_with_image_bitmap_and_dw_and_dh(&image_elem, 0., 0., w, h);
395            }
396            Err(elem) => {
397                let img = elem.dyn_into::<OffscreenCanvas>().expect("OffscreenCanvas");
398                canvas.draw_image_with_offscreen_canvas_and_dw_and_dh(&img, 0., 0., w, h);
399            }
400        }
401        drop(state);
402    }
403}
404
405#[async_trait(?Send)]
406impl CanvasOp for CanvasImageElem {
407    fn prepare(
408        &self,
409        _ts: sk::Transform,
410    ) -> Option<impl core::future::Future<Output = ()> + 'static> {
411        Self::prepare_image(self.image_data.image.clone())
412    }
413
414    async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice) {
415        Self::draw_image(ts, canvas, &self.image_data).await
416    }
417}
418
419/// A glyph element.
420#[derive(Debug)]
421pub struct CanvasGlyphElem {
422    pub fill: ImmutStr,
423    pub upem: Scalar,
424    pub glyph_data: Arc<FlatGlyphItem>,
425}
426
427#[async_trait(?Send)]
428impl CanvasOp for CanvasGlyphElem {
429    fn prepare(
430        &self,
431        ts: sk::Transform,
432    ) -> Option<impl core::future::Future<Output = ()> + 'static> {
433        let _ = ts;
434        match self.glyph_data.as_ref() {
435            FlatGlyphItem::Image(glyph) => {
436                CanvasImageElem::prepare_image(glyph.image.image.clone())
437            }
438            FlatGlyphItem::Outline(..) | FlatGlyphItem::None => None,
439        }
440    }
441
442    async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice) {
443        if ts.sx == 0. || ts.sy == 0. {
444            return;
445        }
446
447        // web_sys::console::log_1(&format!("realize glyph {ts:?}").into());
448
449        let _guard = CanvasStateGuard::new(canvas);
450        match self.glyph_data.as_ref() {
451            #[cfg(not(feature = "rasterize_glyph"))]
452            FlatGlyphItem::Outline(path) => {
453                if !set_transform(canvas, ts) {
454                    return;
455                }
456                canvas.set_fill_style_str(self.fill.as_ref());
457                canvas.fill_with_path_2d(&Path2d::new_with_path_string(&path.d).unwrap());
458            }
459            #[cfg(feature = "rasterize_glyph")]
460            FlatGlyphItem::Outline(path) => {
461                if ts.sx.abs() > 100. || ts.sy.abs() > 100. || ts.kx != 0. || ts.ky != 0. {
462                    if !set_transform(canvas, ts) {
463                        return;
464                    }
465                    canvas.set_fill_style_str(self.fill.as_ref());
466                    canvas.fill_with_path_2d(&Path2d::new_with_path_string(&path.d).unwrap());
467                    return;
468                }
469
470                let x = ts.tx;
471                let y = ts.ty;
472
473                let g = crate::pixglyph_canvas::Glyph::new(&path.d);
474
475                let floor_x = x.floor() as i32;
476                let floor_y = y.floor() as i32;
477                let dx = x - floor_x as f32;
478                let dy = y - floor_y as f32;
479
480                let t = g.rasterize(dx, dy, ts.sx, ts.sy);
481
482                crate::pixglyph_canvas::blend_glyph(
483                    canvas,
484                    &t,
485                    self.fill.as_ref(),
486                    floor_x,
487                    floor_y,
488                );
489            }
490            FlatGlyphItem::Image(glyph) => {
491                if !set_transform(canvas, ts) {
492                    return;
493                }
494                CanvasImageElem::draw_image(ts.pre_concat(glyph.ts.into()), canvas, &glyph.image)
495                    .await
496            }
497            FlatGlyphItem::None => {}
498        }
499    }
500}
501
502#[cfg(feature = "render_bbox")]
503fn render_bbox(canvas: &dyn CanvasDevice, bbox: Option<Rect>, color: &str) {
504    let Some(bbox) = bbox else {
505        return;
506    };
507
508    let _guard = CanvasStateGuard::new(canvas);
509    if !set_transform(canvas, sk::Transform::identity()) {
510        return;
511    }
512    canvas.set_line_width(2.);
513    canvas.set_stroke_style(&color.into());
514    canvas.stroke_rect(
515        bbox.lo.x.0 as f64,
516        bbox.lo.y.0 as f64,
517        bbox.width().0 as f64,
518        bbox.height().0 as f64,
519    );
520}