this repo has no description
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

✨ Allow arbitrary-angle hatch patterns, add transformations support

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
bbcbaef4 c9f2a8b2

+383 -156
+17
Cargo.lock
··· 194 194 ] 195 195 196 196 [[package]] 197 + name = "deunicode" 198 + version = "1.4.4" 199 + source = "registry+https://github.com/rust-lang/crates.io-index" 200 + checksum = "322ef0094744e63628e6f0eb2295517f79276a5b342a4c2ff3042566ca181d4e" 201 + 202 + [[package]] 197 203 name = "digest" 198 204 version = "0.10.7" 199 205 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 685 691 "serde", 686 692 "serde_cbor", 687 693 "serde_json", 694 + "slug", 688 695 "svg", 689 696 "tiny_http", 690 697 "wasm-bindgen", 691 698 "web-sys", 699 + ] 700 + 701 + [[package]] 702 + name = "slug" 703 + version = "0.1.5" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" 706 + dependencies = [ 707 + "deunicode", 708 + "wasm-bindgen", 692 709 ] 693 710 694 711 [[package]]
+1
Cargo.toml
··· 47 47 nanoid = "0.4.0" 48 48 console = { version = "0.15.8", features = ["windows-console-colors"] } 49 49 backtrace = "0.3.71" 50 + slug = "0.1.5" 50 51 51 52 52 53 [dev-dependencies]
+4 -1
src/animation.rs
··· 1 1 use std::fmt::Display; 2 2 3 - use crate::Canvas; 3 + use crate::{Canvas, Layer}; 4 4 5 5 /// Arguments: animation progress (from 0.0 to 1.0), canvas, current ms 6 6 pub type AnimationUpdateFunction = dyn Fn(f32, &mut Canvas, usize) -> anyhow::Result<()>; 7 + 8 + /// An animation that only manipulates a single layer. The layer's render cache is automatically flushed at the end. See `AnimationUpdateFunction` for more information. 9 + pub type LayerAnimationUpdateFunction = dyn Fn(f32, &mut Layer, usize) -> anyhow::Result<()>; 7 10 8 11 pub struct Animation { 9 12 pub name: String,
+9 -16
src/canvas.rs
··· 6 6 use rand::Rng; 7 7 8 8 use crate::{ 9 - layer::Layer, objects::Object, random_color, Color, ColorMapping, ColoredObject, Containable, 10 - Fill, Filter, HatchDirection, LineSegment, ObjectSizes, Point, Region, 9 + layer::Layer, objects::Object, random_color, Angle, Color, ColorMapping, ColoredObject, 10 + Containable, Fill, Filter, LineSegment, ObjectSizes, Point, Region, 11 11 }; 12 12 13 13 #[derive(Debug, Clone)] ··· 218 218 let hatchable = object.hatchable(); 219 219 objects.insert( 220 220 format!("{}#{}", name, i), 221 - ColoredObject( 221 + ColoredObject::from(( 222 222 object, 223 223 if rand::thread_rng().gen_bool(0.5) { 224 224 Some(self.random_fill(hatchable)) 225 225 } else { 226 226 None 227 227 }, 228 - vec![], 229 - ), 228 + )), 230 229 ); 231 230 } 232 231 Layer { ··· 253 252 let hatchable = object.fillable(); 254 253 objects.insert( 255 254 format!("{}#{}", layer_name, i), 256 - ColoredObject( 255 + ColoredObject::from(( 257 256 object, 258 257 if rand::thread_rng().gen_bool(0.5) { 259 258 Some(self.random_fill(hatchable)) 260 259 } else { 261 260 None 262 261 }, 263 - vec![], 264 - ), 262 + )), 265 263 ); 266 264 } 267 265 Layer { ··· 395 393 let hatch_size = rand::thread_rng().gen_range(5..=100) as f32 * 1e-2; 396 394 Fill::Hatched( 397 395 random_color(), 398 - HatchDirection::BottomUpDiagonal, 396 + Angle(rand::thread_rng().gen_range(0.0..360.0)), 399 397 hatch_size, 400 398 // under a certain hatch size, we can't see the hatching if the ratio is not ½ 401 399 if hatch_size < 8.0 { ··· 475 473 fn unique_filters(&self) -> Vec<Filter> { 476 474 self.layers 477 475 .iter() 478 - .flat_map(|layer| layer.objects.iter().flat_map(|(_, o)| o.2.clone())) 476 + .flat_map(|layer| layer.objects.iter().flat_map(|(_, o)| o.filters.clone())) 479 477 .unique() 480 478 .collect() 481 479 } ··· 483 481 fn unique_pattern_fills(&self) -> Vec<Fill> { 484 482 self.layers 485 483 .iter() 486 - .flat_map(|layer| { 487 - layer 488 - .objects 489 - .iter() 490 - .flat_map(|(_, o)| o.1.map(|fill| fill.clone())) 491 - }) 484 + .flat_map(|layer| layer.objects.iter().flat_map(|(_, o)| o.fill)) 492 485 .filter(|fill| matches!(fill, Fill::Hatched(..))) 493 486 .unique_by(|fill| fill.pattern_id()) 494 487 .collect()
+7 -5
src/examples.rs
··· 26 26 27 27 let draw_in = canvas.world_region.resized(-1, -1); 28 28 29 - let splines_area = Region::from_bottomleft(draw_in.bottomleft().translated(2, -1), (3, 3)).unwrap(); 30 - let red_circle_in = Region::from_topright(draw_in.topright().translated(-3, 0), (4, 3)).unwrap(); 29 + let splines_area = 30 + Region::from_bottomleft(draw_in.bottomleft().translated(2, -1), (3, 3)).unwrap(); 31 + let red_circle_in = 32 + Region::from_topright(draw_in.topright().translated(-3, 0), (4, 3)).unwrap(); 31 33 32 34 let red_circle_at = red_circle_in.random_point_within(); 33 35 ··· 64 66 } 65 67 .color(Fill::Hatched( 66 68 Color::White, 67 - HatchDirection::BottomUpDiagonal, 69 + Angle(rand::thread_rng().gen_range(0.0..360.0)), 68 70 (i + 5) as f32 / 10.0, 69 71 0.25, 70 72 )), ··· 85 87 canvas.layers.push(hatches_layer); 86 88 canvas.layers.push(red_dot_friends); 87 89 let mut splines = canvas.n_random_linelikes_within("splines", &splines_area, 30); 88 - for (i, ColoredObject(_, ref mut fill, _)) in splines.objects.values_mut().enumerate() { 89 - *fill = Some(Fill::Solid(if i % 2 == 0 { 90 + for (i, object) in splines.objects.values_mut().enumerate() { 91 + object.fill = Some(Fill::Solid(if i % 2 == 0 { 90 92 Color::Cyan 91 93 } else { 92 94 Color::Pink
+100 -79
src/fill.rs
··· 1 1 use crate::{Color, ColorMapping, RenderCSS}; 2 2 3 + /// Angle, stored in degrees 4 + #[derive(Debug, Clone, Copy, Default)] 5 + pub struct Angle(pub f32); 6 + 7 + impl Angle { 8 + pub const TURN: Self = Angle(360.0); 9 + 10 + pub fn degrees(&self) -> f32 { 11 + self.0 12 + } 13 + 14 + pub fn radians(&self) -> f32 { 15 + self.0 * std::f32::consts::PI / (Self::TURN.0 / 2.0) 16 + } 17 + 18 + pub fn turns(&self) -> f32 { 19 + self.0 / Self::TURN.0 20 + } 21 + 22 + pub fn without_turns(&self) -> Self { 23 + Self(self.0 % Self::TURN.0) 24 + } 25 + } 26 + 27 + impl std::ops::Sub for Angle { 28 + type Output = Angle; 29 + 30 + fn sub(self, rhs: Self) -> Self::Output { 31 + Angle(self.0 - rhs.0) 32 + } 33 + } 34 + 35 + impl std::fmt::Display for Angle { 36 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 37 + write!(f, "{}deg", self.degrees()) 38 + } 39 + } 40 + 3 41 #[derive(Debug, Clone, Copy)] 4 42 pub enum Fill { 5 43 Solid(Color), 6 44 Translucent(Color, f32), 7 - Hatched(Color, HatchDirection, f32, f32), 45 + Hatched(Color, Angle, f32, f32), 8 46 Dotted(Color, f32), 9 47 } 10 48 11 - #[derive(Debug, Clone, Copy)] 12 - pub enum HatchDirection { 13 - Horizontal, 14 - Vertical, 15 - BottomUpDiagonal, 16 - TopDownDiagonal, 49 + // Operations that can be applied on fills. 50 + // Applying them on Option<Fill> is also possible, and will return an Option<Fill>. 51 + pub trait FillOperations { 52 + fn opacify(&self, opacity: f32) -> Self; 53 + fn bottom_up_hatches(color: Color, thickness: f32, spacing: f32) -> Self; 17 54 } 18 55 19 - impl HatchDirection {} 20 - 21 - impl Fill { 22 - pub fn opacify(&self, opacity: f32) -> Self { 56 + impl FillOperations for Fill { 57 + fn opacify(&self, opacity: f32) -> Self { 23 58 match self { 24 59 Fill::Solid(color) => Fill::Translucent(*color, opacity), 25 60 Fill::Translucent(color, _) => Fill::Translucent(*color, opacity), 26 61 _ => self.clone(), 27 62 } 63 + } 64 + 65 + fn bottom_up_hatches(color: Color, thickness: f32, spacing: f32) -> Self { 66 + Fill::Hatched(color, Angle(45.0), thickness, spacing) 67 + } 68 + } 69 + 70 + impl FillOperations for Option<Fill> { 71 + fn opacify(&self, opacity: f32) -> Self { 72 + self.as_ref().map(|fill| fill.opacify(opacity)) 73 + } 74 + 75 + fn bottom_up_hatches(color: Color, thickness: f32, spacing: f32) -> Self { 76 + Some(Fill::bottom_up_hatches(color, thickness, spacing)) 28 77 } 29 78 } 30 79 ··· 63 112 } 64 113 65 114 impl Fill { 66 - pub fn pattern_name(&self) -> String { 67 - match self { 68 - Fill::Hatched(_, direction, ..) => format!( 69 - "hatched-{}", 70 - match direction { 71 - HatchDirection::Horizontal => "horizontal", 72 - HatchDirection::Vertical => "vertical", 73 - HatchDirection::BottomUpDiagonal => "bottom-up", 74 - HatchDirection::TopDownDiagonal => "top-down", 75 - } 76 - ), 77 - _ => String::from(""), 78 - } 79 - } 80 - 81 115 pub fn pattern_id(&self) -> String { 82 - if let Fill::Hatched(color, _, thickness, spacing) = self { 116 + if let Fill::Hatched(color, angle, thickness, spacing) = self { 83 117 return format!( 84 118 "pattern-{}-{}-{}-{}", 85 - self.pattern_name(), 119 + angle, 86 120 color.name(), 87 121 thickness, 88 122 spacing ··· 96 130 colormapping: &ColorMapping, 97 131 ) -> Option<svg::node::element::Pattern> { 98 132 match self { 99 - Fill::Hatched(color, direction, size, thickness_ratio) => { 100 - let root = svg::node::element::Pattern::new() 133 + Fill::Hatched(color, angle, size, thickness_ratio) => { 134 + let thickness = size * (2.0 * thickness_ratio); 135 + 136 + let pattern = svg::node::element::Pattern::new() 101 137 .set("id", self.pattern_id()) 102 - .set("patternUnits", "userSpaceOnUse"); 103 - 104 - let thickness = size * (2.0 * thickness_ratio); 105 - // TODO: to re-center when tickness ratio != ½ 106 - let offset = 0.0; 138 + .set("patternUnits", "userSpaceOnUse") 139 + .set("height", size * 2.0) 140 + .set("width", size * 2.0) 141 + .set("viewBox", format!("0,0,{},{}", size, size)) 142 + .set( 143 + "patternTransform", 144 + format!("rotate({})", (angle.clone() - Angle(45.0)).degrees()), 145 + ) 146 + // https://stackoverflow.com/a/55104220/9943464 147 + .add( 148 + svg::node::element::Polygon::new() 149 + .set( 150 + "points", 151 + format!("0,0 {},0 0,{}", thickness / 2.0, thickness / 2.0), 152 + ) 153 + .set("fill", color.render(colormapping)), 154 + ) 155 + .add( 156 + svg::node::element::Polygon::new() 157 + .set( 158 + "points", 159 + format!( 160 + "0,{} {},0 {},{} {},{}", 161 + size, 162 + size, 163 + size, 164 + thickness / 2.0, 165 + thickness / 2.0, 166 + size, 167 + ), 168 + ) 169 + .set("fill", color.render(colormapping)), 170 + ); 107 171 108 - Some(match direction { 109 - HatchDirection::BottomUpDiagonal => root 110 - // https://stackoverflow.com/a/74205714/9943464 111 - /* 112 - <polygon points="0,0 4,0 0,4" fill="yellow"></polygon> 113 - <polygon points="0,8 8,0 8,4 4,8" fill="yellow"></polygon> 114 - <polygon points="0,4 0,8 8,0 4,0" fill="green"></polygon> 115 - <polygon points="4,8 8,8 8,4" fill="green"></polygon> 116 - */ 117 - .add( 118 - svg::node::element::Polygon::new() 119 - .set( 120 - "points", 121 - format!( 122 - "0,0 {},0 0,{}", 123 - offset + thickness / 2.0, 124 - offset + thickness / 2.0 125 - ), 126 - ) 127 - .set("fill", color.render(colormapping)), 128 - ) 129 - .add( 130 - svg::node::element::Polygon::new() 131 - .set( 132 - "points", 133 - format!( 134 - "0,{} {},0 {},{} {},{}", 135 - offset + size, 136 - offset + size, 137 - offset + size, 138 - offset + thickness / 2.0, 139 - offset + thickness / 2.0, 140 - offset + size, 141 - ), 142 - ) 143 - .set("fill", color.render(colormapping)), 144 - ) 145 - .set("height", size * 2.0) 146 - .set("width", size * 2.0) 147 - .set("viewBox", format!("0,0,{},{}", size, size)), 148 - HatchDirection::Horizontal => todo!(), 149 - HatchDirection::Vertical => todo!(), 150 - HatchDirection::TopDownDiagonal => todo!(), 151 - }) 172 + Some(pattern) 152 173 } 153 174 _ => None, 154 175 }
+1 -1
src/filter.rs
··· 39 39 40 40 pub fn id(&self) -> String { 41 41 format!( 42 - "{}-{}", 42 + "filter-{}-{}", 43 43 self.name(), 44 44 self.parameter.to_string().replace(".", "_") 45 45 )
+9 -16
src/layer.rs
··· 36 36 37 37 pub fn remove_all_objects_in(&mut self, region: &Region) { 38 38 self.objects 39 - .retain(|_, ColoredObject(o, ..)| !o.region().within(region)) 39 + .retain(|_, ColoredObject { object, .. }| !object.region().within(region)) 40 40 } 41 41 42 42 pub fn paint_all_objects(&mut self, fill: Fill) { 43 - for (_id, ColoredObject(_, ref mut maybe_fill, _)) in &mut self.objects { 44 - *maybe_fill = Some(fill.clone()); 43 + for (_id, obj) in &mut self.objects { 44 + obj.fill = Some(fill.clone()); 45 45 } 46 46 self.flush(); 47 47 } 48 48 49 49 pub fn filter_all_objects(&mut self, filter: Filter) { 50 - for (_id, ColoredObject(_, _, ref mut filters)) in &mut self.objects { 51 - filters.push(filter) 50 + for (_id, obj) in &mut self.objects { 51 + obj.filters.push(filter) 52 52 } 53 53 self.flush(); 54 54 } ··· 56 56 pub fn move_all_objects(&mut self, dx: i32, dy: i32) { 57 57 self.objects 58 58 .iter_mut() 59 - .for_each(|(_, ColoredObject(obj, _, _))| obj.translate(dx, dy)); 59 + .for_each(|(_, ColoredObject { object, .. })| object.translate(dx, dy)); 60 60 self.flush(); 61 61 } 62 62 ··· 81 81 self.objects 82 82 .get_mut(name) 83 83 .ok_or(format!("Object '{}' not found", name))? 84 - .2 84 + .filters 85 85 .push(filter); 86 86 87 87 self.flush(); ··· 113 113 .set("class", "layer") 114 114 .set("data-layer", self.name.clone()); 115 115 116 - for (id, ColoredObject(object, maybe_fill, filters)) in &self.objects { 117 - layer_group = layer_group.add(object.render( 118 - cell_size, 119 - object_sizes, 120 - &colormap, 121 - &id, 122 - *maybe_fill, 123 - filters, 124 - )); 116 + for (id, obj) in &self.objects { 117 + layer_group = layer_group.add(obj.render(cell_size, object_sizes, &colormap, &id)); 125 118 } 126 119 127 120 self._render_cache = Some(layer_group.clone());
+20
src/lib.rs
··· 15 15 pub mod preview; 16 16 pub mod region; 17 17 pub mod sync; 18 + pub mod transform; 18 19 pub mod ui; 19 20 pub mod video; 20 21 pub mod web; ··· 31 32 pub use point::*; 32 33 pub use region::*; 33 34 pub use sync::Syncable; 35 + pub use transform::*; 34 36 pub use video::*; 35 37 pub use web::log; 36 38 ··· 167 169 duration, 168 170 Animation::new(format!("unnamed animation {}", nanoid!()), f), 169 171 ); 172 + } 173 + 174 + pub fn animate_layer( 175 + &mut self, 176 + layer: &'static str, 177 + duration: usize, 178 + f: &'static LayerAnimationUpdateFunction, 179 + ) { 180 + let animation = Animation { 181 + name: format!("unnamed animation {}", nanoid!()), 182 + update: Box::new(move |progress, canvas, ms| { 183 + (f)(progress, canvas.layer(layer), ms)?; 184 + canvas.layer(layer).flush(); 185 + Ok(()) 186 + }), 187 + }; 188 + 189 + self.start_animation(duration, animation); 170 190 } 171 191 } 172 192
-1
src/main.rs
··· 1 1 use anyhow::Result; 2 - use itertools::Itertools; 3 2 use shapemaker::{ 4 3 cli::{canvas_from_cli, cli_args}, 5 4 *,
+113 -28
src/objects.rs
··· 1 - use crate::{ColorMapping, Fill, Filter, Point, Region}; 1 + use std::collections::HashMap; 2 + 3 + use crate::{ColorMapping, Fill, Filter, Point, Region, Transformation}; 2 4 use itertools::Itertools; 3 5 use wasm_bindgen::prelude::*; 4 6 ··· 36 38 } 37 39 38 40 #[derive(Debug, Clone)] 39 - pub struct ColoredObject(pub Object, pub Option<Fill>, pub Vec<Filter>); 41 + pub struct ColoredObject { 42 + pub object: Object, 43 + pub fill: Option<Fill>, 44 + pub filters: Vec<Filter>, 45 + pub transformations: Vec<Transformation>, 46 + } 40 47 41 48 impl ColoredObject { 42 49 pub fn filter(mut self, filter: Filter) -> Self { 43 - self.2.push(filter); 50 + self.filters.push(filter); 44 51 self 45 52 } 46 53 47 54 pub fn clear_filters(&mut self) { 48 - self.2.clear(); 55 + self.filters.clear(); 49 56 } 50 57 51 - pub fn fill(&self) -> Option<Fill> { 52 - self.1 58 + pub fn render( 59 + &self, 60 + cell_size: usize, 61 + object_sizes: ObjectSizes, 62 + colormap: &ColorMapping, 63 + id: &str, 64 + ) -> svg::node::element::Group { 65 + let group = self.object.render(cell_size, object_sizes, id); 66 + 67 + let rendered_transforms = self 68 + .transformations 69 + .render_attribute(colormap, !self.object.fillable()); 70 + 71 + let mut css = String::new(); 72 + if !matches!(self.object, Object::RawSVG(..)) { 73 + css = self.fill.render_css(colormap, !self.object.fillable()); 74 + } 75 + 76 + css += self 77 + .filters 78 + .iter() 79 + .map(|f| f.render_fill_css(colormap)) 80 + .into_iter() 81 + .join(" ") 82 + .as_ref(); 83 + 84 + group 85 + .set("style", css) 86 + .set(rendered_transforms.0, rendered_transforms.1) 53 87 } 54 88 } 55 89 56 90 impl std::fmt::Display for ColoredObject { 57 91 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 58 - let ColoredObject(obj, fill, filters) = self; 92 + let ColoredObject { 93 + object, 94 + fill, 95 + filters, 96 + transformations, 97 + } = self; 98 + 59 99 if fill.is_some() { 60 - write!(f, "{:?} {:?}", fill.unwrap(), obj)?; 100 + write!(f, "{:?} {:?}", fill.unwrap(), object)?; 61 101 } else { 62 - write!(f, "transparent {:?}", obj)?; 102 + write!(f, "transparent {:?}", object)?; 63 103 } 64 104 65 105 if !filters.is_empty() { 66 106 write!(f, " with filters {:?}", filters)?; 67 107 } 108 + 109 + if !transformations.is_empty() { 110 + write!(f, " with transformations {:?}", transformations)?; 111 + } 112 + 68 113 Ok(()) 69 114 } 70 115 } 71 116 72 117 impl From<Object> for ColoredObject { 73 118 fn from(value: Object) -> Self { 74 - ColoredObject(value, None, vec![]) 119 + ColoredObject { 120 + object: value, 121 + fill: None, 122 + filters: vec![], 123 + transformations: vec![], 124 + } 75 125 } 76 126 } 77 127 78 128 impl From<(Object, Option<Fill>)> for ColoredObject { 79 - fn from(value: (Object, Option<Fill>)) -> Self { 80 - ColoredObject(value.0, value.1, vec![]) 129 + fn from((object, fill): (Object, Option<Fill>)) -> Self { 130 + ColoredObject { 131 + object, 132 + fill, 133 + filters: vec![], 134 + transformations: vec![], 135 + } 81 136 } 82 137 } 83 138 ··· 98 153 dot_radius: 2.0, 99 154 default_line_width: 2.0, 100 155 } 156 + } 157 + } 158 + 159 + pub trait RenderAttribute { 160 + const MULTIPLE_VALUES_JOIN_BY: &'static str = ", "; 161 + 162 + fn render_fill_attribute(&self, colormap: &ColorMapping) -> (String, String); 163 + fn render_stroke_attribute(&self, colormap: &ColorMapping) -> (String, String); 164 + fn render_attribute( 165 + &self, 166 + colormap: &ColorMapping, 167 + fill_as_stroke_color: bool, 168 + ) -> (String, String) { 169 + if fill_as_stroke_color { 170 + self.render_stroke_attribute(colormap) 171 + } else { 172 + self.render_fill_attribute(colormap) 173 + } 174 + } 175 + } 176 + impl<T: RenderAttribute> RenderAttribute for Vec<T> { 177 + fn render_fill_attribute(&self, colormap: &ColorMapping) -> (String, String) { 178 + ( 179 + self.first() 180 + .unwrap() 181 + .render_fill_attribute(colormap) 182 + .0 183 + .clone(), 184 + self.iter() 185 + .map(|v| v.render_fill_attribute(colormap).1.clone()) 186 + .join(", "), 187 + ) 188 + } 189 + 190 + fn render_stroke_attribute(&self, colormap: &ColorMapping) -> (String, String) { 191 + ( 192 + self.first() 193 + .unwrap() 194 + .render_stroke_attribute(colormap) 195 + .0 196 + .clone(), 197 + self.iter() 198 + .map(|v| v.render_stroke_attribute(colormap).1.clone()) 199 + .join(", "), 200 + ) 101 201 } 102 202 } 103 203 ··· 220 320 &self, 221 321 cell_size: usize, 222 322 object_sizes: ObjectSizes, 223 - colormap: &ColorMapping, 224 323 id: &str, 225 - fill: Option<Fill>, 226 - filter: &Vec<Filter>, 227 324 ) -> svg::node::element::Group { 228 325 let group = svg::node::element::Group::new(); 229 326 ··· 239 336 Object::RawSVG(..) => self.render_raw_svg(), 240 337 }; 241 338 242 - let mut css = String::new(); 243 - if !matches!(self, Object::RawSVG(..)) { 244 - css = fill.render_css(colormap, !self.fillable()); 245 - } 246 - 247 - css += filter 248 - .iter() 249 - .map(|f| f.render_fill_css(colormap)) 250 - .into_iter() 251 - .join(" ") 252 - .as_ref(); 253 - 254 - group.set("data-object", id).add(rendered).set("style", css) 339 + group.set("data-object", id).add(rendered) 255 340 } 256 341 257 342 fn render_raw_svg(&self) -> Box<dyn svg::node::Node> {
+93
src/transform.rs
··· 1 + use slug::slugify; 2 + use wasm_bindgen::prelude::*; 3 + 4 + use crate::RenderAttribute; 5 + 6 + #[wasm_bindgen] 7 + #[derive(Debug, Clone, Copy, PartialEq)] 8 + pub enum TransformationType { 9 + Scale, 10 + Rotate, 11 + Skew, 12 + Matrix, 13 + } 14 + 15 + #[wasm_bindgen(getter_with_clone)] 16 + #[derive(Debug, Clone)] 17 + pub struct TransformationWASM { 18 + pub kind: TransformationType, 19 + pub parameters: Vec<f32>, 20 + } 21 + 22 + #[derive(Debug, Clone, Copy, PartialEq)] 23 + pub enum Transformation { 24 + Scale(f32, f32), 25 + Rotate(f32), 26 + Skew(f32, f32), 27 + Matrix(f32, f32, f32, f32, f32, f32), 28 + } 29 + 30 + impl From<TransformationWASM> for Transformation { 31 + fn from(transformation: TransformationWASM) -> Self { 32 + match transformation.kind { 33 + TransformationType::Scale => { 34 + Transformation::Scale(transformation.parameters[0], transformation.parameters[1]) 35 + } 36 + TransformationType::Rotate => Transformation::Rotate(transformation.parameters[0]), 37 + TransformationType::Skew => { 38 + Transformation::Skew(transformation.parameters[0], transformation.parameters[1]) 39 + } 40 + TransformationType::Matrix => Transformation::Matrix( 41 + transformation.parameters[0], 42 + transformation.parameters[1], 43 + transformation.parameters[2], 44 + transformation.parameters[3], 45 + transformation.parameters[4], 46 + transformation.parameters[5], 47 + ), 48 + } 49 + } 50 + } 51 + 52 + impl Transformation { 53 + pub fn name(&self) -> String { 54 + match self { 55 + Transformation::Matrix(..) => "matrix", 56 + Transformation::Rotate(..) => "rotate", 57 + Transformation::Scale(..) => "scale", 58 + Transformation::Skew(..) => "skew", 59 + } 60 + .to_owned() 61 + } 62 + 63 + #[allow(non_snake_case)] 64 + pub fn ScaleUniform(scale: f32) -> Self { 65 + Transformation::Scale(scale, scale) 66 + } 67 + 68 + pub fn id(&self) -> String { 69 + slugify(format!("{:?}", self)) 70 + } 71 + } 72 + 73 + impl RenderAttribute for Transformation { 74 + const MULTIPLE_VALUES_JOIN_BY: &'static str = " "; 75 + 76 + fn render_fill_attribute(&self, _colormap: &crate::ColorMapping) -> (String, String) { 77 + ( 78 + "transform".to_owned(), 79 + match self { 80 + Transformation::Scale(x, y) => format!("scale({} {})", x, y), 81 + Transformation::Rotate(angle) => format!("rotate({})", angle), 82 + Transformation::Skew(x, y) => format!("skewX({}) skewY({})", x, y), 83 + Transformation::Matrix(a, b, c, d, e, f) => { 84 + format!("matrix({}, {}, {}, {}, {}, {})", a, b, c, d, e, f) 85 + } 86 + }, 87 + ) 88 + } 89 + 90 + fn render_stroke_attribute(&self, colormap: &crate::ColorMapping) -> (String, String) { 91 + self.render_fill_attribute(colormap) 92 + } 93 + }
+3 -3
src/ui.rs
··· 5 5 use std::time; 6 6 7 7 pub const PROGRESS_BARS_STYLE: &str = 8 - "{prefix:>12.bold.cyan} [{bar:40}] {pos}/{len}: {msg} ({eta} left)"; 8 + "{prefix:>12.bold.cyan} [{bar:25}] {pos}/{len}: {msg} ({eta} left)"; 9 9 10 10 pub struct Spinner { 11 11 pub spinner: ProgressBar, ··· 60 60 } 61 61 62 62 pub fn format_log_msg(verb: &'static str, message: &str) -> String { 63 - let style = Style::new().bold().cyan(); 64 - format!("{}: {}", style.apply_to(format!("{verb:>12}")), message) 63 + let style = Style::new().bold().green(); 64 + format!("{} {}", style.apply_to(format!("{verb:>12}")), message) 65 65 } 66 66 67 67 impl Log for ProgressBar {
+6 -6
src/video.rs
··· 575 575 .trim_start_matches(&command.name) 576 576 .trim() 577 577 .to_string(); 578 - (command.action)(args, &mut canvas, &mut context); 578 + (command.action)(args, &mut canvas, &mut context)?; 579 579 } 580 580 } 581 581 } ··· 638 638 let mut frame_writer_threads = vec![]; 639 639 let mut frames_to_write: Vec<(String, usize, usize)> = vec![]; 640 640 641 - remove_dir_all(self.frames_output_directory); 642 - create_dir(self.frames_output_directory).unwrap(); 643 - create_dir_all(Path::new(&output_file).parent().unwrap()).unwrap(); 641 + remove_dir_all(self.frames_output_directory)?; 642 + create_dir(self.frames_output_directory)?; 643 + create_dir_all(Path::new(&output_file).parent().unwrap())?; 644 644 645 645 let total_frames = self.total_frames(); 646 646 let aspect_ratio = ··· 674 674 std::fs::write( 675 675 format!("{}/{}.svg", self.frames_output_directory, no), 676 676 &frame, 677 - ); 677 + )?; 678 678 } 679 679 680 680 let chunk_size = (frames_to_write.len() as f32 / workers_count as f32).ceil() as usize; ··· 697 697 frames_output_directory, 698 698 aspect_ratio, 699 699 resolution, 700 - ); 700 + ).unwrap(); 701 701 progress_bar.inc(1); 702 702 } 703 703 })