From 5f91f5a2207fbcefea8e0c0e8b4c441acbc35c30 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:30:16 +0800 Subject: [PATCH] feat: add vec2bbox converter (#529) * dev: draft to bbox * cargo --- Cargo.lock | 1 + core/Cargo.toml | 1 + core/src/vector/pass/vec2bbox.rs | 123 +++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 core/src/vector/pass/vec2bbox.rs diff --git a/Cargo.lock b/Cargo.lock index f888f3fb..569e86e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4507,6 +4507,7 @@ dependencies = [ "serde_with", "sha2", "siphasher 1.0.1", + "svgtypes", "tiny-skia", "tiny-skia-path", "ttf-parser", diff --git a/core/Cargo.toml b/core/Cargo.toml index 4d0c376c..0656b8eb 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -43,6 +43,7 @@ serde_with.workspace = true rayon.workspace = true rkyv = { workspace = true, optional = true } tiny-skia-path.workspace = true +svgtypes.workspace = true wasm-bindgen = { workspace = true, optional = true } web-sys = { workspace = true, optional = true } diff --git a/core/src/vector/pass/vec2bbox.rs b/core/src/vector/pass/vec2bbox.rs new file mode 100644 index 00000000..892de82c --- /dev/null +++ b/core/src/vector/pass/vec2bbox.rs @@ -0,0 +1,123 @@ +use std::collections::HashMap; + +use reflexo::{hash::Fingerprint, vector::ir::*}; + +#[derive(Default)] +pub struct Vec2BBoxPass { + bbox_caches: HashMap<(Fingerprint, Transform), Option>, +} + +impl Vec2BBoxPass { + /// Calculate the bounding box of a vector item with a given transform. + /// The transform is required to calculate the accurate bounding box for + /// irregular shapes. + pub fn bbox_of(&mut self, module: &Module, v: Fingerprint, ts: Transform) -> Option { + if let Some(bbox) = self.bbox_caches.get(&(v, ts)) { + return *bbox; + } + + let bbox = self.bbox_of_(module, v, ts); + println!("bbox_of({v:?}, {ts:?}) = {bbox:?}"); + self.bbox_caches.insert((v, ts), bbox); + bbox + } + + fn bbox_of_(&mut self, module: &Module, v: Fingerprint, ts: Transform) -> Option { + let item = module.get_item(&v).unwrap(); + match item { + VecItem::Item(item) => self.bbox_of(module, item.1, item.0.clone().into()), + VecItem::Group(g) => { + let mut r = Rect::default(); + for (p, f) in g.0.iter() { + let sub_bbox = self.bbox_of(module, *f, ts); + if let Some(sub_bbox) = sub_bbox { + union(&mut r, *p, sub_bbox); + } + } + Some(r) + } + VecItem::Image(ImageItem { size, .. }) | VecItem::Link(LinkItem { size, .. }) => { + self.rect(*size, ts) + } + // todo: I'm writing this in my leg + VecItem::Text(t) => { + let width = t.width(); + let height = t.shape.size.0; + tiny_skia_path::Rect::from_xywh(0.0, 0.0, width.0, height).map(|e| e.into()) + } + VecItem::Path(p) => self.path_bbox(p, ts), + VecItem::ContentHint(..) + | VecItem::ColorTransform(..) + | VecItem::Pattern(..) + | VecItem::Gradient(..) + | VecItem::Color32(..) + | VecItem::None => None, + } + } + + pub fn path_bbox(&mut self, p: &PathItem, ts: Transform) -> Option { + let d = convert_path(&p.d); + d.and_then(|e| e.transform(ts.into())) + .and_then(|e| e.compute_tight_bounds()) + .and_then(|e| { + let stroke = p.styles.iter().find_map(|s| match s { + PathStyle::StrokeWidth(w) => Some(w.0), + _ => None, + })?; + // extend the bounding box by the stroke width + let x = e.x() - stroke; + let y = e.y() - stroke; + let w = e.width() + stroke * 2.0; + let h = e.height() + stroke * 2.0; + tiny_skia_path::Rect::from_xywh(x, y, w, h).map(|e| e.into()) + }) + } + + fn rect(&self, size: Axes, ts: Transform) -> Option { + let r = tiny_skia_path::Rect::from_xywh(0.0, 0.0, size.x.0, size.y.0); + r.and_then(|e| e.transform(ts.into())).map(|e| e.into()) + } +} + +fn union(r: &mut Rect, p: Axes, sub_bbox: Rect) { + *r = r.union(&sub_bbox.translate(p)) +} + +fn convert_path(path_data: &str) -> Option { + let mut builder = tiny_skia_path::PathBuilder::new(); + for segment in svgtypes::SimplifyingPathParser::from(path_data) { + let segment = match segment { + Ok(v) => v, + Err(_) => break, + }; + + match segment { + svgtypes::SimplePathSegment::MoveTo { x, y } => { + builder.move_to(x as f32, y as f32); + } + svgtypes::SimplePathSegment::LineTo { x, y } => { + builder.line_to(x as f32, y as f32); + } + svgtypes::SimplePathSegment::Quadratic { x1, y1, x, y } => { + builder.quad_to(x1 as f32, y1 as f32, x as f32, y as f32); + } + svgtypes::SimplePathSegment::CurveTo { + x1, + y1, + x2, + y2, + x, + y, + } => { + builder.cubic_to( + x1 as f32, y1 as f32, x2 as f32, y2 as f32, x as f32, y as f32, + ); + } + svgtypes::SimplePathSegment::ClosePath => { + builder.close(); + } + } + } + + builder.finish() +}