Skip to main content

lb_rs/model/svg/
buffer.rs

1use std::collections::HashMap;
2use std::fmt::Write;
3
4use bezier_rs::{Bezier, Subpath};
5use glam::{DAffine2, DMat2, DVec2};
6use indexmap::IndexMap;
7use serde::{Deserialize, Serialize};
8use usvg::fontdb::Database;
9use usvg::tiny_skia_path::{PathSegment, Point};
10use usvg::{Options, Paint, Transform};
11use uuid::Uuid;
12
13use super::WeakTransform;
14use super::diff::DiffState;
15use super::element::{
16    Color, DynamicColor, Element, ManipulatorGroupId, Path, Stroke, WeakImage, WeakImages,
17    WeakPathPressures,
18};
19
20const ZOOM_G_ID: &str = "lb_master_transform";
21const WEAK_IMAGE_G_ID: &str = "lb_images";
22const WEAK_PATH_PRESSURES_G_ID: &str = "lb_path_pressures";
23const WEAK_VIEWPORT_SETTINGS_G_ID: &str = "lb_viewport_settings";
24
25#[derive(Default, Clone)]
26pub struct Buffer {
27    pub elements: IndexMap<Uuid, Element>,
28    pub weak_images: WeakImages,
29    pub weak_path_pressures: WeakPathPressures,
30    pub weak_viewport_settings: WeakViewportSettings,
31    pub master_transform_changed: bool,
32    pub id_map: HashMap<Uuid, String>,
33}
34
35#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
36pub struct WeakRect {
37    pub min: (f32, f32),
38    pub max: (f32, f32),
39}
40
41#[derive(Clone, Default, Copy, Serialize, Deserialize)]
42pub struct WeakViewportSettings {
43    /// the drawable rect in the master-transformed plane
44    pub bounded_rect: Option<WeakRect>,
45    pub master_transform: WeakTransform,
46    pub viewport_transform: Option<WeakTransform>,
47    pub left_locked: bool,
48    pub right_locked: bool,
49    pub bottom_locked: bool,
50    pub top_locked: bool,
51}
52
53impl Buffer {
54    pub fn new(content: &str) -> Self {
55        let mut elements = IndexMap::default();
56        let mut id_map = HashMap::default();
57        let mut weak_images = WeakImages::default();
58        let mut weak_path_pressures = WeakPathPressures::default();
59        let mut weak_viewport_settings = WeakViewportSettings::default();
60
61        let maybe_tree = usvg::Tree::from_str(content, &Options::default(), &Database::default());
62
63        if let Err(err) = maybe_tree {
64            println!("{err:#?}");
65        } else {
66            let utree = maybe_tree.unwrap();
67
68            utree.root().children().iter().for_each(|u_el| {
69                parse_child(
70                    u_el,
71                    &mut elements,
72                    &mut weak_viewport_settings,
73                    &mut id_map,
74                    &mut weak_images,
75                    &mut weak_path_pressures,
76                )
77            });
78        }
79
80        Self {
81            elements,
82            id_map,
83            weak_images,
84            weak_viewport_settings,
85            weak_path_pressures,
86            master_transform_changed: false,
87        }
88    }
89
90    pub fn reload(
91        local_elements: &mut IndexMap<Uuid, Element>, local_weak_images: &mut WeakImages,
92        local_weak_pressures: &mut WeakPathPressures,
93        local_viewport_settings: &mut WeakViewportSettings, base_buffer: &Self,
94        remote_buffer: &Self,
95    ) {
96        // todo: convert weak images
97        for (id, base_img) in base_buffer.weak_images.iter() {
98            if let Some(remote_img) = remote_buffer.weak_images.get(id) {
99                if remote_img != base_img {
100                    local_weak_images.insert(*id, *remote_img);
101                }
102            } else {
103                // this was deleted remotly
104                local_weak_images.remove(id);
105                local_elements.shift_remove(id);
106            }
107        }
108
109        for (id, remote_img) in remote_buffer.weak_images.iter() {
110            if !base_buffer.weak_images.contains_key(id) {
111                local_weak_images.insert(*id, *remote_img);
112            }
113        }
114
115        let local_master_transform = Transform::from(local_viewport_settings.master_transform);
116
117        base_buffer
118            .elements
119            .iter()
120            .filter_map(|(id, el)| if let Element::Path(p) = el { Some((id, p)) } else { None })
121            .for_each(|(id, base_path)| {
122                if let Some(Element::Path(remote_path)) = remote_buffer.elements.get(id) {
123                    if remote_path != base_path {
124                        // this element was changed remotly
125                        let mut transformed_path = remote_path.clone();
126                        transformed_path.diff_state.transformed = Some(local_master_transform);
127                        transformed_path.diff_state.transformed = None;
128                        transformed_path
129                            .data
130                            .apply_transform(u_transform_to_bezier(&local_master_transform));
131                        transformed_path.diff_state.data_changed = true;
132
133                        local_elements.insert(*id, Element::Path(transformed_path.clone()));
134
135                        if let Some(remote_pressure) = remote_buffer.weak_path_pressures.get(id) {
136                            local_weak_pressures.insert(*id, remote_pressure.clone());
137                        }
138                    }
139                } else {
140                    // this was deleted remotely
141                    local_elements.shift_remove(id);
142                    local_weak_pressures.remove(id);
143                }
144            });
145
146        // strokes new in remote go after the nearest preceding stroke that local still has
147        let mut cursor = 0;
148        for (id, el) in remote_buffer.elements.iter() {
149            let Element::Path(remote_el) = el else { continue };
150            if base_buffer.elements.contains_key(id) {
151                if let Some(pos) = local_elements.get_index_of(id) {
152                    cursor = pos + 1;
153                }
154            } else if !local_elements.contains_key(id) {
155                let mut transformed_path = remote_el.clone();
156                transformed_path.diff_state.transformed = Some(local_master_transform);
157                transformed_path
158                    .data
159                    .apply_transform(u_transform_to_bezier(&local_master_transform));
160
161                transformed_path.diff_state.data_changed = true;
162                transformed_path.diff_state.transformed = None;
163
164                local_elements.insert_before(cursor, *id, Element::Path(transformed_path));
165                cursor += 1;
166                if let Some(base_pressure) = remote_buffer.weak_path_pressures.get(id) {
167                    local_weak_pressures.insert(*id, base_pressure.clone());
168                }
169            }
170        }
171    }
172
173    pub fn insert(&mut self, id: Uuid, mut el: Element) {
174        match el {
175            Element::Path(ref mut path) => {
176                path.diff_state.data_changed = true;
177                path.diff_state.transformed = None
178            }
179            Element::Image(ref mut image) => {
180                image.diff_state.data_changed = true;
181                image.diff_state.transformed = None
182            }
183            _ => {}
184        }
185        self.elements.insert_before(0, id, el);
186    }
187
188    pub fn hard_remove(&mut self, id: Uuid) {
189        self.elements.shift_remove(&id);
190    }
191
192    /// soft remove that marks the element as deleted but retains it in memeory
193    pub fn remove(&mut self, id: Uuid) {
194        if let Some(el) = self.elements.get_mut(&id) {
195            match el {
196                Element::Path(path) => {
197                    path.deleted = true;
198                    path.diff_state.delete_changed = true;
199                }
200                Element::Image(image) => {
201                    image.deleted = true;
202                    image.diff_state.delete_changed = true;
203                }
204                _ => {}
205            }
206        }
207    }
208    pub fn serialize(&self) -> String {
209        serialize_inner(
210            &self.id_map,
211            &self.elements,
212            &self.weak_viewport_settings,
213            &self.weak_images,
214            &self.weak_path_pressures,
215        )
216    }
217}
218
219pub fn serialize_inner(
220    id_map: &HashMap<Uuid, String>, elements: &IndexMap<Uuid, Element>,
221    weak_viewport_settings: &WeakViewportSettings, buffer_weak_images: &WeakImages,
222    weak_pressures: &WeakPathPressures,
223) -> String {
224    let mut root = r#"<svg xmlns="http://www.w3.org/2000/svg">"#.into();
225    let mut weak_images = WeakImages::default();
226    let master_transform = Transform::from(weak_viewport_settings.master_transform);
227
228    for (index, el) in elements.iter().enumerate() {
229        match el.1 {
230            Element::Path(p) => {
231                if p.deleted {
232                    continue;
233                }
234                let mut curv_attrs = " ".to_string();
235                // if it's empty then the curve will not be converted to string via bezier_rs
236                if let Some(stroke) = p.stroke {
237                    curv_attrs = format!(
238                        "stroke-width='{}' stroke='rgba({},{},{},{})' fill='none' id='{}' transform='{}'",
239                        stroke.width,
240                        stroke.color.light.red,
241                        stroke.color.light.green,
242                        stroke.color.light.blue,
243                        stroke.opacity,
244                        id_map.get(el.0).unwrap_or(&el.0.to_string()),
245                        to_svg_transform(p.transform)
246                    );
247                }
248
249                let mut data = p.data.clone();
250                data.apply_transform(u_transform_to_bezier(
251                    &master_transform.invert().unwrap_or_default(),
252                ));
253
254                if data.len() > 1 {
255                    data.to_svg(&mut root, curv_attrs, "".into(), "".into(), "".into())
256                }
257            }
258            Element::Image(img) => {
259                if img.deleted {
260                    continue;
261                }
262
263                let mut weak_image: WeakImage = img.into_weak(index);
264
265                weak_image.transform(master_transform.invert().unwrap_or_default());
266
267                weak_images.insert(*el.0, weak_image);
268            }
269            Element::Text(_) => {}
270        }
271    }
272
273    let zoom_level = format!(
274        r#"<g id="{}" transform="matrix({} {} {} {} {} {})"></g>"#,
275        ZOOM_G_ID,
276        master_transform.sx,
277        master_transform.kx,
278        master_transform.ky,
279        master_transform.sy,
280        master_transform.tx,
281        master_transform.ty
282    );
283
284    weak_images.extend(buffer_weak_images.iter());
285
286    if !weak_images.is_empty() {
287        let binary_data = bincode::serialize(&weak_images).expect("Failed to serialize");
288        let base64_data = base64::encode(&binary_data);
289
290        let _ = write!(&mut root, "<g id=\"{WEAK_IMAGE_G_ID}\"> <g id=\"{base64_data}\"></g></g>");
291    }
292
293    if !weak_pressures.is_empty() {
294        let binary_data = bincode::serialize(&weak_pressures).expect("Failed to serialize");
295        let base64_data = base64::encode(&binary_data);
296
297        let _ = write!(
298            &mut root,
299            "<g id=\"{WEAK_PATH_PRESSURES_G_ID}\"> <g id=\"{base64_data}\"></g></g>"
300        );
301    }
302
303    let binary_data = bincode::serialize(&weak_viewport_settings).expect("Failed to serialize");
304    let base64_data = base64::encode(&binary_data);
305
306    let _ = write!(
307        &mut root,
308        "<g id=\"{WEAK_VIEWPORT_SETTINGS_G_ID}\"> <g id=\"{base64_data}\"></g></g>"
309    );
310
311    let _ = write!(&mut root, "{zoom_level} </svg>");
312    root
313}
314
315pub fn parse_child(
316    u_el: &usvg::Node, elements: &mut IndexMap<Uuid, Element>,
317    weak_viewport_settings: &mut WeakViewportSettings, id_map: &mut HashMap<Uuid, String>,
318    weak_images: &mut WeakImages, weak_path_pressures: &mut WeakPathPressures,
319) {
320    match &u_el {
321        usvg::Node::Group(group) => {
322            if group.id().eq(WEAK_IMAGE_G_ID) {
323                if let Some(usvg::Node::Group(weak_images_g)) = group.children().first() {
324                    let base64 = base64::decode(weak_images_g.id().as_bytes())
325                        .expect("Failed to decode base64");
326
327                    let decoded: WeakImages = bincode::deserialize(&base64).unwrap_or_default();
328                    *weak_images = decoded;
329                }
330            } else if group.id().eq(WEAK_PATH_PRESSURES_G_ID) {
331                if let Some(usvg::Node::Group(weak_pressures_g)) = group.children().first() {
332                    let base64 = base64::decode(weak_pressures_g.id().as_bytes())
333                        .expect("Failed to decode base64");
334
335                    let decoded: WeakPathPressures =
336                        bincode::deserialize(&base64).unwrap_or_default();
337                    *weak_path_pressures = decoded;
338                }
339            } else if group.id().eq(WEAK_VIEWPORT_SETTINGS_G_ID) {
340                if let Some(usvg::Node::Group(weak_viewport_settings_g)) = group.children().first()
341                {
342                    let base64 = base64::decode(weak_viewport_settings_g.id().as_bytes())
343                        .expect("Failed to decode base64");
344
345                    let decoded: WeakViewportSettings =
346                        bincode::deserialize(&base64).unwrap_or_default();
347                    *weak_viewport_settings = decoded;
348                }
349            } else {
350                group.children().iter().for_each(|u_el| {
351                    parse_child(
352                        u_el,
353                        elements,
354                        weak_viewport_settings,
355                        id_map,
356                        weak_images,
357                        weak_path_pressures,
358                    )
359                });
360            }
361        }
362
363        usvg::Node::Image(_) => {}
364        usvg::Node::Path(path) => {
365            let diff_state = DiffState { data_changed: true, ..Default::default() };
366
367            let id = get_internal_id(path.id(), id_map);
368
369            let stroke = if let Some(s) = path.stroke() {
370                if let Paint::Color(color) = *s.paint() {
371                    let maybe_dynamic_color =
372                        get_dyn_color(Color::new_rgb(color.red, color.green, color.blue));
373
374                    Some(Stroke {
375                        color: maybe_dynamic_color,
376                        opacity: s.opacity().get(),
377                        width: s.width().get(),
378                    })
379                } else {
380                    None
381                }
382            } else {
383                None
384            };
385            let mut data = usvg_d_to_subpath(path);
386
387            data.apply_transform(u_transform_to_bezier(
388                &path.abs_transform().invert().unwrap_or_default(),
389            ));
390
391            elements.insert(
392                id,
393                Element::Path(Path {
394                    data,
395                    visibility: path.visibility(),
396                    fill: path.fill().cloned(),
397                    stroke,
398                    transform: path.abs_transform(),
399                    diff_state,
400                    deleted: false,
401                    opacity: 1.0,
402                }),
403            );
404        }
405        _ => {}
406    }
407}
408
409pub fn get_dyn_color(color: Color) -> DynamicColor {
410    let canvas_colors = get_canvas_colors();
411
412    let maybe_dynamic_color = if let Some(dynamic_color) = canvas_colors
413        .iter()
414        .find(|c| c.light.eq(&color) || c.dark.eq(&color))
415    {
416        *dynamic_color
417    } else {
418        DynamicColor { light: color, dark: color }
419    };
420    maybe_dynamic_color
421}
422
423fn get_internal_id(svg_id: &str, id_map: &mut HashMap<Uuid, String>) -> Uuid {
424    let id: Uuid = svg_id.parse().unwrap_or(Uuid::new_v4());
425
426    if id_map.insert(id, svg_id.to_owned()).is_some() {
427        warn!(id = svg_id, "found elements  with duplicate id");
428    }
429    id
430}
431
432pub fn get_canvas_colors() -> Vec<DynamicColor> {
433    let mut highlighter_colors = get_highlighter_colors();
434    highlighter_colors.append(&mut get_pen_colors());
435
436    highlighter_colors
437}
438
439pub fn get_highlighter_colors() -> Vec<DynamicColor> {
440    let yellow =
441        DynamicColor { light: Color::new_rgb(244, 250, 65), dark: Color::new_rgb(244, 250, 65) };
442    let blue =
443        DynamicColor { light: Color::new_rgb(65, 194, 250), dark: Color::new_rgb(65, 194, 250) };
444    let pink =
445        DynamicColor { light: Color::new_rgb(254, 110, 175), dark: Color::new_rgb(254, 110, 175) };
446    vec![yellow, blue, pink]
447}
448
449pub fn get_pen_colors() -> Vec<DynamicColor> {
450    let red =
451        DynamicColor { light: Color::new_rgb(218, 21, 21), dark: Color::new_rgb(174, 33, 33) };
452    let orange =
453        DynamicColor { light: Color::new_rgb(255, 149, 0), dark: Color::new_rgb(255, 159, 10) };
454    let yellow =
455        DynamicColor { light: Color::new_rgb(255, 204, 0), dark: Color::new_rgb(255, 214, 10) };
456    let green =
457        DynamicColor { light: Color::new_rgb(42, 136, 49), dark: Color::new_rgb(56, 176, 65) };
458    let teal =
459        DynamicColor { light: Color::new_rgb(0, 128, 128), dark: Color::new_rgb(0, 147, 147) };
460    let cyan =
461        DynamicColor { light: Color::new_rgb(85, 190, 240), dark: Color::new_rgb(90, 200, 245) };
462    let blue =
463        DynamicColor { light: Color::new_rgb(62, 130, 230), dark: Color::new_rgb(54, 116, 207) };
464    let indigo =
465        DynamicColor { light: Color::new_rgb(75, 0, 130), dark: Color::new_rgb(64, 0, 110) };
466    let purple =
467        DynamicColor { light: Color::new_rgb(128, 0, 128), dark: Color::new_rgb(147, 0, 147) };
468    let magenta =
469        DynamicColor { light: Color::new_rgb(175, 82, 222), dark: Color::new_rgb(191, 90, 242) };
470    let pink =
471        DynamicColor { light: Color::new_rgb(255, 105, 180), dark: Color::new_rgb(255, 120, 190) };
472    let brown =
473        DynamicColor { light: Color::new_rgb(139, 69, 19), dark: Color::new_rgb(101, 53, 17) };
474
475    let fg = DynamicColor { light: Color::black(), dark: Color::white() };
476
477    vec![fg, red, orange, yellow, green, teal, cyan, blue, indigo, purple, brown, magenta, pink]
478}
479
480pub fn get_background_colors() -> Vec<DynamicColor> {
481    let pastel_blue =
482        DynamicColor { light: Color::new_rgb(226, 235, 240), dark: Color::new_rgb(5, 15, 26) };
483
484    let soft_pink_beige =
485        DynamicColor { light: Color::new_rgb(240, 230, 230), dark: Color::new_rgb(25, 16, 14) };
486
487    let warm_gray =
488        DynamicColor { light: Color::new_rgb(238, 236, 230), dark: Color::new_rgb(22, 21, 20) };
489
490    let nice_green =
491        DynamicColor { light: Color::new_rgb(226, 240, 228), dark: Color::new_rgb(10, 23, 16) };
492
493    let bg = DynamicColor { light: Color::white(), dark: Color::black() };
494
495    vec![soft_pink_beige, warm_gray, nice_green, pastel_blue, bg]
496}
497
498fn usvg_d_to_subpath(path: &usvg::Path) -> Subpath<ManipulatorGroupId> {
499    let mut prev = Point::default();
500    let mut subpath: Subpath<ManipulatorGroupId> = Subpath::new(vec![], false);
501    for segment in path.data().segments() {
502        match segment {
503            PathSegment::MoveTo(p) => {
504                prev = p;
505            }
506            PathSegment::CubicTo(p1, p2, p3) => {
507                let bez = Bezier::from_cubic_coordinates(
508                    prev.x.into(),
509                    prev.y.into(),
510                    p1.x.into(),
511                    p1.y.into(),
512                    p2.x.into(),
513                    p2.y.into(),
514                    p3.x.into(),
515                    p3.y.into(),
516                );
517                subpath.append_bezier(&bez, bezier_rs::AppendType::IgnoreStart);
518                prev = p3;
519            }
520            PathSegment::LineTo(p) => {
521                let bez = Bezier::from_linear_coordinates(
522                    prev.x.into(),
523                    prev.y.into(),
524                    p.x.into(),
525                    p.y.into(),
526                );
527                subpath.append_bezier(&bez, bezier_rs::AppendType::IgnoreStart);
528                prev = p;
529            }
530            _ => {}
531        }
532    }
533
534    let t = path.abs_transform();
535
536    subpath.apply_transform(DAffine2 {
537        matrix2: DMat2 {
538            x_axis: DVec2 { x: t.sx.into(), y: t.ky.into() },
539            y_axis: DVec2 { x: t.kx.into(), y: t.sy.into() },
540        },
541        translation: DVec2 { x: t.tx.into(), y: t.ty.into() },
542    });
543
544    subpath
545}
546
547pub fn u_transform_to_bezier(src: &Transform) -> DAffine2 {
548    glam::DAffine2 {
549        matrix2: DMat2 {
550            x_axis: DVec2 { x: src.sx.into(), y: src.ky.into() },
551            y_axis: DVec2 { x: src.kx.into(), y: src.sy.into() },
552        },
553        translation: glam::DVec2 { x: src.tx.into(), y: src.ty.into() },
554    }
555}
556
557fn to_svg_transform(transform: Transform) -> String {
558    format!(
559        "matrix({} {} {} {} {} {})",
560        transform.sx, transform.ky, transform.kx, transform.sy, transform.tx, transform.ty
561    )
562}