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
30pub type CanvasNode = Arc<CanvasElem>;
32type Context2d = web_sys::CanvasRenderingContext2d;
34
35#[async_trait(?Send)]
38pub trait CanvasOp {
39 fn prepare(
41 &self,
42 ts: sk::Transform,
43 ) -> Option<impl core::future::Future<Output = ()> + Sized + 'static>;
44 async fn realize(&self, ts: sk::Transform, canvas: &dyn CanvasDevice);
46}
47
48#[derive(Debug)]
50pub enum CanvasElem {
51 Group(CanvasGroupElem),
53 Clip(CanvasClipElem),
55 Path(CanvasPathElem),
57 Image(CanvasImageElem),
59 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#[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 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#[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#[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 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 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 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 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#[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 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#[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 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}