this repo has no description
3
fork

Configure Feed

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

♻️ Get rid of CenterAnchor and Anchor, use Points everywhere + new title example

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
f7641ae5 d00370d2

+337 -347
-99
src/anchors.rs
··· 1 - use wasm_bindgen::prelude::wasm_bindgen; 2 - 3 - use crate::Point; 4 - 5 - #[wasm_bindgen] 6 - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 7 - pub struct Anchor(pub i32, pub i32); 8 - 9 - impl Anchor { 10 - pub fn translate(&mut self, dx: i32, dy: i32) { 11 - self.0 += dx; 12 - self.1 += dy; 13 - } 14 - 15 - pub fn translated(&self, dx: i32, dy: i32) -> Self { 16 - Anchor(self.0 + dx, self.1 + dy) 17 - } 18 - 19 - pub fn distances(&self, other: &Anchor) -> (usize, usize) { 20 - ( 21 - self.0.abs_diff(other.0) as usize, 22 - self.1.abs_diff(other.1) as usize, 23 - ) 24 - } 25 - } 26 - 27 - impl From<(i32, i32)> for Anchor { 28 - fn from(value: (i32, i32)) -> Self { 29 - Anchor(value.0, value.1) 30 - } 31 - } 32 - 33 - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 34 - #[wasm_bindgen] 35 - pub struct CenterAnchor(pub i32, pub i32); 36 - 37 - impl From<(usize, usize)> for CenterAnchor { 38 - fn from(value: (usize, usize)) -> Self { 39 - CenterAnchor(value.0 as i32, value.1 as i32) 40 - } 41 - } 42 - 43 - impl From<(usize, usize)> for Anchor { 44 - fn from(value: (usize, usize)) -> Self { 45 - Anchor(value.0 as i32, value.1 as i32) 46 - } 47 - } 48 - 49 - impl CenterAnchor { 50 - pub fn translate(&mut self, dx: i32, dy: i32) { 51 - self.0 += dx; 52 - self.1 += dy; 53 - } 54 - } 55 - 56 - pub trait Coordinates { 57 - fn coords(&self, cell_size: usize) -> (f32, f32); 58 - fn center() -> Self; 59 - } 60 - 61 - impl Coordinates for Anchor { 62 - fn coords(&self, cell_size: usize) -> (f32, f32) { 63 - match self { 64 - Anchor(-1, -1) => (cell_size as f32 / 2.0, cell_size as f32 / 2.0), 65 - Anchor(i, j) => { 66 - let x = (i * cell_size as i32) as f32; 67 - let y = (j * cell_size as i32) as f32; 68 - (x, y) 69 - } 70 - } 71 - } 72 - 73 - fn center() -> Self { 74 - Anchor(-1, -1) 75 - } 76 - } 77 - 78 - impl Coordinates for CenterAnchor { 79 - fn coords(&self, cell_size: usize) -> (f32, f32) { 80 - match self { 81 - CenterAnchor(-1, -1) => ((cell_size / 2) as f32, (cell_size / 2) as f32), 82 - CenterAnchor(i, j) => { 83 - let x = *i as f32 * cell_size as f32 + cell_size as f32 / 2.0; 84 - let y = *j as f32 * cell_size as f32 + cell_size as f32 / 2.0; 85 - (x, y) 86 - } 87 - } 88 - } 89 - 90 - fn center() -> Self { 91 - CenterAnchor(-1, -1) 92 - } 93 - } 94 - 95 - impl From<Point> for CenterAnchor { 96 - fn from(value: Point) -> Self { 97 - CenterAnchor(value.0 as i32, value.1 as i32) 98 - } 99 - }
+84 -58
src/canvas.rs
··· 1 1 use core::panic; 2 - use std::{cmp, collections::HashMap, io::Write, ops::Range}; 2 + use std::{ 3 + cmp, 4 + collections::HashMap, 5 + io::{empty, Write}, 6 + ops::Range, 7 + }; 3 8 4 9 use chrono::DateTime; 5 10 use itertools::Itertools; 6 11 use rand::Rng; 7 - use svg::node::element::Pattern; 8 12 9 13 use crate::{ 10 - layer::Layer, objects::Object, random_color, web::console_log, Anchor, CenterAnchor, Color, 11 - ColorMapping, ColoredObject, Containable, Fill, Filter, HatchDirection, LineSegment, 12 - ObjectSizes, Point, Region, 14 + layer::Layer, objects::Object, random_color, Color, ColorMapping, ColoredObject, Containable, 15 + Fill, Filter, HatchDirection, LineSegment, ObjectSizes, Point, Region, 13 16 }; 14 17 15 18 #[derive(Debug, Clone)] ··· 55 58 self.grid_size = (new_width, new_height); 56 59 self.world_region = Region { 57 60 start: Point(0, 0), 58 - end: self.grid_size.into(), 61 + end: Point::from(self.grid_size).translated(-1, -1), 59 62 }; 60 63 } 61 64 ··· 65 68 66 69 pub fn layer(&mut self, name: &str) -> &mut Layer { 67 70 self.layer_safe(name).unwrap() 71 + } 72 + 73 + pub fn new_layer(&mut self, name: &str) -> &mut Layer { 74 + if self.layer_exists(name) { 75 + panic!("Layer {} already exists", name); 76 + } 77 + 78 + self.layers.push(Layer::new(name)); 79 + self.layer(name) 80 + } 81 + 82 + pub fn layer_or_empty(&mut self, name: &str) -> &mut Layer { 83 + if self.layer_exists(name) { 84 + return self.layer(name); 85 + } 86 + 87 + self.new_layer(name) 88 + } 89 + 90 + pub fn layer_exists(&self, name: &str) -> bool { 91 + self.layers.iter().any(|layer| layer.name == name) 68 92 } 69 93 70 94 pub fn ensure_layer_exists(&self, name: &str) { 71 - self.layers 72 - .iter() 73 - .find(|layer| layer.name == name) 74 - .or_else(|| panic!("Layer {} does not exist", name)); 95 + if !self.layer_exists(name) { 96 + panic!("Layer {} does not exist", name); 97 + } 75 98 } 76 99 77 100 /// puts this layer on top, and the others below, without changing their order ··· 113 136 match self.layer_safe(layer) { 114 137 None => Err(format!("Layer {} does not exist", layer)), 115 138 Some(layer) => { 116 - layer 117 - .objects 118 - .insert(name.to_string(), (object, fill).into()); 139 + layer.add_object(name, (object, fill).into()); 119 140 Ok(()) 120 141 } 121 142 } ··· 234 255 } 235 256 236 257 pub fn random_object_within(&self, region: &Region) -> Object { 237 - let start = self.random_anchor(region); 258 + let start = self.random_point(region); 238 259 match rand::thread_rng().gen_range(1..=7) { 239 260 1 => self.random_polygon(region), 240 - 2 => Object::BigCircle(self.random_center_anchor(region)), 261 + 2 => Object::BigCircle(start), 241 262 3 => Object::SmallCircle(start), 242 263 4 => Object::Dot(start), 243 264 5 => Object::CurveInward( ··· 251 272 self.object_sizes.default_line_width, 252 273 ), 253 274 7 => Object::Line( 254 - self.random_anchor(region), 255 - self.random_anchor(region), 275 + self.random_point(region), 276 + self.random_point(region), 256 277 self.object_sizes.default_line_width, 257 278 ), 258 279 _ => unreachable!(), ··· 260 281 } 261 282 262 283 pub fn random_linelike_within(&self, region: &Region) -> Object { 263 - let start = self.random_anchor(region); 284 + let start = self.random_point(region); 264 285 match rand::thread_rng().gen_range(1..=3) { 265 286 1 => Object::CurveInward( 266 287 start, ··· 273 294 self.object_sizes.default_line_width, 274 295 ), 275 296 3 => Object::Line( 276 - self.random_anchor(region), 277 - self.random_anchor(region), 297 + self.random_point(region), 298 + self.random_point(region), 278 299 self.object_sizes.default_line_width, 279 300 ), 280 301 _ => unreachable!(), 281 302 } 282 303 } 283 304 284 - pub fn random_end_anchor(&self, start: Anchor, region: &Region) -> Anchor { 305 + pub fn random_end_anchor(&self, start: Point, region: &Region) -> Point { 285 306 // End anchors are always a square diagonal from the start anchor (for now) 286 307 // that means taking steps of the form n * (one of (1, 1), (1, -1), (-1, 1), (-1, -1)) 287 308 // Except that the end anchor needs to stay in the bounds of the shape. ··· 289 310 // Determine all possible end anchors that are in a square diagonal from the start anchor 290 311 let mut possible_end_anchors = vec![]; 291 312 292 - for x in region.mirrored_width_range() { 293 - for y in region.mirrored_height_range() { 294 - let end_anchor = Anchor(start.0 + x, start.1 + y); 313 + // shapes can end on the next cell, since that's where they end 314 + let actual_region = region.enlarged(1, 1); 315 + 316 + for x in actual_region.mirrored_width_range() { 317 + for y in actual_region.mirrored_height_range() { 318 + let end_anchor = start.translated(x, y); 295 319 296 320 if end_anchor == start { 297 321 continue; 298 322 } 299 323 300 324 // Check that the end anchor is in a square diagonal from the start anchor and that the end anchor is in bounds 301 - if x.abs() == y.abs() && region.contains(&end_anchor) { 325 + if x.abs() == y.abs() && actual_region.contains(&end_anchor) { 302 326 possible_end_anchors.push(end_anchor); 303 327 } 304 328 } ··· 310 334 311 335 pub fn random_polygon(&self, region: &Region) -> Object { 312 336 let number_of_anchors = rand::thread_rng().gen_range(self.polygon_vertices_range.clone()); 313 - let start = self.random_anchor(region); 337 + let start = self.random_point(region); 314 338 let mut lines: Vec<LineSegment> = vec![]; 315 339 for _ in 0..number_of_anchors { 316 - let next_anchor = self.random_anchor(region); 340 + let next_anchor = self.random_point(region); 317 341 lines.push(self.random_line(next_anchor)); 318 342 } 319 343 Object::Polygon(start, lines) 320 344 } 321 345 322 - pub fn random_line(&self, end: Anchor) -> LineSegment { 346 + pub fn random_line(&self, end: Point) -> LineSegment { 323 347 match rand::thread_rng().gen_range(1..=3) { 324 348 1 => LineSegment::Straight(end), 325 349 2 => LineSegment::InwardCurve(end), ··· 332 356 region.start == (0, 0) && region.end == self.grid_size 333 357 } 334 358 335 - pub fn random_anchor(&self, region: &Region) -> Anchor { 336 - if self.region_is_whole_grid(region) 337 - && rand::thread_rng().gen_bool(1.0 / (self.grid_size.0 * self.grid_size.1) as f64) 338 - { 339 - // small change of getting center (-1, -1) even when grid size would not permit it (e.g. 4x4) 340 - Anchor(-1, -1) 341 - } else { 342 - Anchor( 343 - rand::thread_rng().gen_range(region.x_range()) as i32, 344 - rand::thread_rng().gen_range(region.y_range()) as i32, 345 - ) 346 - } 347 - } 348 - 349 - pub fn random_center_anchor(&self, region: &Region) -> CenterAnchor { 350 - if self.region_is_whole_grid(region) 351 - && rand::thread_rng().gen_bool( 352 - 1.0 / ((self.grid_size.0 as i32 - 1) * (self.grid_size.1 as i32 - 1)) as f64, 353 - ) 354 - { 355 - // small change of getting center (-1, -1) even when grid size would not permit it (e.g. 3x3) 356 - CenterAnchor(-1, -1) 357 - } else { 358 - CenterAnchor( 359 - rand::thread_rng().gen_range(region.x_range_without_last()) as i32, 360 - rand::thread_rng().gen_range(region.y_range_without_last()) as i32, 361 - ) 362 - } 359 + pub fn random_point(&self, region: &Region) -> Point { 360 + Point( 361 + rand::thread_rng().gen_range(region.x_range()), 362 + rand::thread_rng().gen_range(region.y_range()), 363 + ) 363 364 } 364 365 365 366 pub fn random_fill(&self, hatchable: bool) -> Fill { ··· 428 429 429 430 impl Canvas { 430 431 pub fn width(&self) -> usize { 431 - self.cell_size * (self.grid_size.0 - 1) + 2 * self.canvas_outter_padding 432 + self.cell_size * self.world_region.width() + 2 * self.canvas_outter_padding 432 433 } 433 434 434 435 pub fn height(&self) -> usize { 435 - self.cell_size * (self.grid_size.1 - 1) + 2 * self.canvas_outter_padding 436 + self.cell_size * self.world_region.height() + 2 * self.canvas_outter_padding 436 437 } 437 438 438 439 pub fn aspect_ratio(&self) -> f32 { ··· 468 469 .filter(|fill| matches!(fill, Fill::Hatched(..))) 469 470 .unique_by(|fill| fill.pattern_id()) 470 471 .collect() 472 + } 473 + 474 + pub fn debug_region(&mut self, region: &Region, color: Color) { 475 + let layer = self.layer_or_empty("debug plane"); 476 + 477 + layer.add_object( 478 + format!("{}_corner_ss", region).as_str(), 479 + Object::Dot(region.topleft()).color(Fill::Solid(color)), 480 + ); 481 + layer.add_object( 482 + format!("{}_corner_se", region).as_str(), 483 + Object::Dot(region.topright().translated(1, 0)).color(Fill::Solid(color)), 484 + ); 485 + layer.add_object( 486 + format!("{}_corner_ne", region).as_str(), 487 + Object::Dot(region.bottomright().translated(1, 1)).color(Fill::Solid(color)), 488 + ); 489 + layer.add_object( 490 + format!("{}_corner_nw", region).as_str(), 491 + Object::Dot(region.bottomleft().translated(0, 1)).color(Fill::Solid(color)), 492 + ); 493 + layer.add_object( 494 + format!("{}_region", region).as_str(), 495 + Object::Rectangle(region.start, region.end).color(Fill::Translucent(color, 0.25)), 496 + ) 471 497 } 472 498 473 499 pub fn render(&mut self, layers: &Vec<&str>, render_background: bool) -> String {
+39 -12
src/examples.rs
··· 24 24 canvas.set_background(Color::Black); 25 25 let mut hatches_layer = Layer::new("root"); 26 26 27 - let draw_in = canvas.world_region.resized(-1, -1).enlarged(-2, -2); 28 - let splines_area = 29 - Region::from_bottomleft(canvas.world_region.bottomleft(), (3, 3)).translated(3, -3); 27 + let draw_in = canvas.world_region.resized(-1, -1); 28 + 29 + let splines_area = Region::from_bottomleft(draw_in.bottomleft().translated(2, -1), (3, 3)); 30 30 let red_circle_in = Region::from(( 31 31 Point(splines_area.topright().0 + 3, draw_in.topright().1), 32 32 draw_in.bottomright(), 33 33 )); 34 34 35 - println!("splines_area: {:?}", splines_area); 36 - println!("red_circle_in: {:?}", red_circle_in); 37 - 38 35 let red_circle_at = red_circle_in.random_point_within(); 39 36 40 - println!("Red circle at {:?}", red_circle_at); 41 - 42 37 for (i, point) in draw_in.iter().enumerate() { 38 + println!("{}", point); 43 39 if splines_area.contains(&point) { 40 + println!("skipping {} has its contained in {}", point, splines_area); 44 41 continue; 45 42 } 46 43 47 44 if point == red_circle_at { 45 + println!("adding red circle at {} instead of sqr", point); 48 46 hatches_layer.add_object( 49 47 "redpoint", 50 48 Object::BigCircle(point.into()) ··· 59 57 hatches_layer.add_object( 60 58 &format!("{}-{}", x, y), 61 59 if rand::thread_rng().gen_bool(0.5) { 62 - Object::BigCircle((x, y).into()) 60 + Object::BigCircle(point) 63 61 } else { 64 - // XXX the .translated is a hack, centeranchor needs to disappear 65 - Object::Rectangle((x, y).into(), Anchor::from((x, y)).translated(1, 1)) 62 + Object::Rectangle(point, point) 66 63 } 67 64 .color(Fill::Hatched( 68 65 Color::White, 69 66 HatchDirection::BottomUpDiagonal, 70 - (i + 1) as f32 / 10.0, 67 + (i + 5) as f32 / 10.0, 71 68 0.25, 72 69 )), 73 70 ); 74 71 } 72 + println!("{:?}", hatches_layer.objects.keys()); 75 73 canvas.layers.push(hatches_layer); 76 74 let mut splines = canvas.n_random_linelikes_within("splines", &splines_area, 30); 77 75 for (i, ColoredObject(_, ref mut fill, _)) in splines.objects.values_mut().enumerate() { ··· 83 81 } 84 82 splines.filter_all_objects(Filter::glow(4.0)); 85 83 canvas.layers.push(splines); 84 + 85 + canvas 86 + } 87 + 88 + pub fn title() -> Canvas { 89 + let mut canvas = dna_analysis_machine(); 90 + let text_zone = Region::from_topleft(Point(8, 2), (3, 3)); 91 + canvas.remove_all_objects_in(&text_zone); 92 + let last_letter_at = &text_zone.bottomright().translated(1, 0); 93 + canvas.remove_all_objects_in(&last_letter_at.region()); 94 + 95 + let text_layer = canvas.new_layer("title"); 96 + 97 + let title = String::from("shapemaker"); 98 + 99 + for (i, point) in text_zone 100 + .iter() 101 + .chain(last_letter_at.region().iter()) 102 + .enumerate() 103 + { 104 + println!("{}: {} '{}'", i, point, &title[i..i + 1]); 105 + let character = title[i..i + 1].to_owned(); 106 + 107 + text_layer.add_object( 108 + &i.to_string(), 109 + Object::CenteredText(point, character, 30.0).color(Fill::Solid(Color::White)), 110 + ); 111 + } 112 + 86 113 canvas 87 114 }
-1
src/fill.rs
··· 1 - use std::hash::Hash; 2 1 3 2 use crate::{Color, ColorMapping, RenderCSS}; 4 3
+5 -4
src/layer.rs
··· 1 - use crate::{ColorMapping, ColoredObject, Containable, Fill, Filter, Object, ObjectSizes, Region}; 2 - use anyhow::Context; 1 + use crate::{ColorMapping, ColoredObject, Fill, Filter, ObjectSizes, Region}; 3 2 use std::collections::HashMap; 4 - use wasm_bindgen::prelude::*; 5 - use web_sys::js_sys::RegExp; 6 3 7 4 #[derive(Debug, Clone, Default)] 8 5 // #[wasm_bindgen(getter_with_clone)] ··· 64 61 } 65 62 66 63 pub fn add_object(&mut self, name: &str, object: ColoredObject) { 64 + if self.objects.contains_key(name) { 65 + panic!("object {} already exists in layer {}", name, self.name); 66 + } 67 + 67 68 self.objects.insert(name.to_string(), object); 68 69 self.flush(); 69 70 }
+2 -2
src/lib.rs
··· 4 4 mod objects; 5 5 pub use color::*; 6 6 pub use objects::*; 7 - mod anchors; 8 7 mod fill; 9 - pub use anchors::*; 8 + mod point; 9 + pub use point::*; 10 10 pub use fill::*; 11 11 mod region; 12 12 pub use region::*;
+3 -10
src/main.rs
··· 1 1 use itertools::Itertools; 2 - use rand::Rng; 3 2 use shapemaker::{ 4 3 cli::{canvas_from_cli, cli_args}, 5 4 *, ··· 13 12 let mut canvas = canvas_from_cli(&args); 14 13 15 14 if args.cmd_image && !args.cmd_video { 16 - // canvas.root().add_object( 17 - // "hello", 18 - // Object::Text(Anchor(3, 4), "hello world!".into(), 16.0) 19 - // .color(Fill::Solid(Color::Black)), 20 - // ); 21 - // canvas.set_background(Color::White); 22 - canvas = examples::dna_analysis_machine(); 15 + canvas = examples::title(); 23 16 let rendered = canvas.render(&vec!["*"], true); 24 17 if args.arg_file.ends_with(".svg") { 25 18 std::fs::write(args.arg_file, rendered).unwrap(); ··· 53 46 let mut kicks = Layer::new("anchor kick"); 54 47 55 48 let fill = Fill::Translucent(Color::White, 0.0); 56 - let circle_at = |x: usize, y: usize| Object::SmallCircle(Anchor(x as i32, y as i32)); 49 + let circle_at = |x: usize, y: usize| Object::SmallCircle(Point(x, y)); 57 50 58 51 let (end_x, end_y) = { 59 52 let Point(x, y) = canvas.world_region.end; ··· 66 59 canvas.add_or_replace_layer(kicks); 67 60 68 61 let mut ch = Layer::new("ch"); 69 - ch.add_object("0", Object::Dot(Anchor(0, 0)).into()); 62 + ch.add_object("0", Object::Dot(Point(0, 0)).into()); 70 63 canvas.add_or_replace_layer(ch); 71 64 }) 72 65 .sync_audio_with(&args.flag_sync_with.unwrap())
-16
src/midi.rs
··· 253 253 254 254 for (_ms, notes) in stem_notes.iter().sorted_by_key(|(ms, _)| *ms) { 255 255 for (track_name, note) in notes { 256 - // println!( 257 - // "{} {} {:?}", 258 - // { 259 - // let duration = chrono::Duration::milliseconds(*ms as i64); 260 - // format!( 261 - // "{}'{}.{}\"#{}", 262 - // duration.num_minutes(), 263 - // duration.num_seconds() % 60, 264 - // duration.num_milliseconds() % 1000, 265 - // note.tick, 266 - // ) 267 - // }, 268 - // track_name, 269 - // note 270 - // ); 271 - 272 256 result 273 257 .entry(track_name.clone()) 274 258 .or_default()
+97 -35
src/objects.rs
··· 1 - use crate::{Anchor, CenterAnchor, ColorMapping, Coordinates, Fill, Filter, Point, Region}; 1 + use crate::{ColorMapping, Fill, Filter, Point, Region}; 2 2 use itertools::Itertools; 3 - use svg::Node; 4 3 use wasm_bindgen::prelude::*; 5 4 6 5 #[derive(Debug, Clone, PartialEq, Eq)] 7 6 pub enum LineSegment { 8 - Straight(Anchor), 9 - InwardCurve(Anchor), 10 - OutwardCurve(Anchor), 7 + Straight(Point), 8 + InwardCurve(Point), 9 + OutwardCurve(Point), 11 10 } 12 11 13 12 #[derive(Debug, Clone)] 14 13 pub enum Object { 15 - Polygon(Anchor, Vec<LineSegment>), 16 - Line(Anchor, Anchor, f32), 17 - CurveOutward(Anchor, Anchor, f32), 18 - CurveInward(Anchor, Anchor, f32), 19 - SmallCircle(Anchor), 20 - Dot(Anchor), 21 - BigCircle(CenterAnchor), 22 - Text(Anchor, String, f32), 23 - Rectangle(Anchor, Anchor), 14 + Polygon(Point, Vec<LineSegment>), 15 + Line(Point, Point, f32), 16 + CurveOutward(Point, Point, f32), 17 + CurveInward(Point, Point, f32), 18 + SmallCircle(Point), 19 + Dot(Point), 20 + BigCircle(Point), 21 + Text(Point, String, f32), 22 + CenteredText(Point, String, f32), 23 + // FittedText(Region, String), 24 + Rectangle(Point, Point), 24 25 RawSVG(Box<dyn svg::Node>), 25 26 } 26 27 ··· 48 49 } 49 50 } 50 51 52 + impl std::fmt::Display for ColoredObject { 53 + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 54 + let ColoredObject(obj, fill, filters) = self; 55 + if fill.is_some() { 56 + write!(f, "{:?} {:?}", fill.unwrap(), obj)?; 57 + } else { 58 + write!(f, "transparent {:?}", obj)?; 59 + } 60 + 61 + if !filters.is_empty() { 62 + write!(f, " with filters {:?}", filters)?; 63 + } 64 + Ok(()) 65 + } 66 + } 67 + 51 68 impl From<Object> for ColoredObject { 52 69 fn from(value: Object) -> Self { 53 70 ColoredObject(value, None, vec![]) ··· 112 129 start.translate(dx, dy); 113 130 end.translate(dx, dy); 114 131 } 115 - Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 116 - anchor.translate(dx, dy) 117 - } 132 + Object::Text(anchor, _, _) 133 + | Object::CenteredText(anchor, ..) 134 + | Object::Dot(anchor) 135 + | Object::SmallCircle(anchor) => anchor.translate(dx, dy), 118 136 Object::BigCircle(center) => center.translate(dx, dy), 119 137 Object::RawSVG(_) => { 120 138 unimplemented!() ··· 156 174 | Object::CurveInward(start, end, _) 157 175 | Object::CurveOutward(start, end, _) 158 176 | Object::Rectangle(start, end) => (start, end).into(), 159 - Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 160 - (anchor, anchor).into() 161 - } 162 - Object::BigCircle(center) => Region::from((center, center)), 177 + Object::Text(anchor, _, _) 178 + | Object::CenteredText(anchor, ..) 179 + | Object::Dot(anchor) 180 + | Object::SmallCircle(anchor) => anchor.region(), 181 + Object::BigCircle(center) => center.region(), 163 182 Object::RawSVG(_) => { 164 183 unimplemented!() 165 184 } ··· 188 207 fill: Option<Fill>, 189 208 filter: &Vec<Filter>, 190 209 ) -> svg::node::element::Group { 191 - let mut group = svg::node::element::Group::new(); 210 + let group = svg::node::element::Group::new(); 192 211 193 212 let rendered = match self { 194 - Object::Text(..) => self.render_text(cell_size), 213 + Object::Text(..) | Object::CenteredText(..) => self.render_text(cell_size), 195 214 Object::Rectangle(..) => self.render_rectangle(cell_size), 196 215 Object::Polygon(..) => self.render_polygon(cell_size), 197 216 Object::Line(..) => self.render_line(cell_size), ··· 226 245 } 227 246 228 247 fn render_text(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 229 - if let Object::Text(position, content, font_size) = self { 230 - return Box::new( 231 - svg::node::element::Text::new(content.clone()) 232 - .set("x", position.coords(cell_size).0) 233 - .set("y", position.coords(cell_size).1) 234 - .set("font-size", format!("{}pt", font_size)) 235 - .set("font-family", "sans-serif") 248 + if let Object::Text(position, content, font_size) 249 + | Object::CenteredText(position, content, font_size) = self 250 + { 251 + let centered = matches!(self, Object::CenteredText(..)); 252 + 253 + let coords = if centered { 254 + position.center_coords(cell_size) 255 + } else { 256 + position.coords(cell_size) 257 + }; 258 + 259 + let mut node = svg::node::element::Text::new(content.clone()) 260 + .set("x", coords.0) 261 + .set("y", coords.1) 262 + .set("font-size", format!("{}pt", font_size)) 263 + .set("font-family", "Victor Mono"); 264 + 265 + if centered { 266 + node = node 236 267 .set("text-anchor", "middle") 237 - .set("dominant-baseline", "middle"), 238 - ); 268 + // FIXME does not work with imagemagick 269 + .set("dominant-baseline", "middle"); 270 + } else { 271 + // FIXME does not work with imagemagick 272 + // see https://legacy.imagemagick.org/discourse-server/viewtopic.php?t=31540 273 + node = node.set("dominant-baseline", "hanging") 274 + } 275 + 276 + return Box::new(node); 239 277 } 240 278 241 279 panic!("Expected Text, got {:?}", self); 242 280 } 281 + 282 + // fn render_fitted_text(&self, cell_size: usize) -> Box<dyn svg:node::Node> { 283 + // if let Object::FittedText(region, content) = self { 284 + // let (x, y) = region.start.coords(cell_size); 285 + // let width = region.width() * cell_size as f32; 286 + // let height = region.height() * cell_size as f32; 287 + 288 + // return Box::new( 289 + // svg::node::element::Text::new(content.clone()) 290 + // .set("x", x) 291 + // .set("y", y) 292 + // .set("") 293 + // .set("font-size", format!("{}pt", 10.0)) 294 + // .set("font-family", "sans-serif"), 295 + // ); 296 + // } 297 + 298 + // panic!("Expected FittedText, got {:?}", self); 299 + // } 243 300 244 301 fn render_rectangle(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 245 302 if let Object::Rectangle(start, end) = self { ··· 390 447 } 391 448 392 449 fn render_big_circle(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 393 - if let Object::BigCircle(center) = self { 450 + if let Object::BigCircle(topleft) = self { 451 + let (cx, cy) = { 452 + let (x, y) = topleft.coords(cell_size); 453 + (x + cell_size as f32 / 2.0, y + cell_size as f32 / 2.0) 454 + }; 455 + 394 456 return Box::new( 395 457 svg::node::element::Circle::new() 396 - .set("cx", center.coords(cell_size).0) 397 - .set("cy", center.coords(cell_size).1) 458 + .set("cx", cx) 459 + .set("cy", cy) 398 460 .set("r", cell_size / 2), 399 461 ); 400 462 }
+69
src/point.rs
··· 1 + use wasm_bindgen::prelude::*; 2 + 3 + use crate::Region; 4 + 5 + #[wasm_bindgen] 6 + #[derive(Debug, Clone, Copy, Default, PartialEq)] 7 + pub struct Point(pub usize, pub usize); 8 + 9 + impl Point { 10 + pub fn translated(&self, dx: i32, dy: i32) -> Self { 11 + Self((self.0 as i32 + dx) as usize, (self.1 as i32 + dy) as usize) 12 + } 13 + 14 + pub fn translated_by(&self, point: Point) -> Self { 15 + Self(self.0 + point.0, self.1 + point.1) 16 + } 17 + 18 + pub fn region(&self) -> Region { 19 + Region { 20 + start: self.clone(), 21 + end: self.clone(), 22 + } 23 + } 24 + 25 + pub fn translate(&mut self, dx: i32, dy: i32) { 26 + self.0 = (self.0 as i32 + dx) as usize; 27 + self.1 = (self.1 as i32 + dy) as usize; 28 + } 29 + 30 + pub fn coords(&self, cell_size: usize) -> (f32, f32) { 31 + ((self.0 * cell_size) as f32, (self.1 * cell_size) as f32) 32 + } 33 + 34 + /// get SVG coordinates of the cell's center instead of its origin (top-left) 35 + pub fn center_coords(&self, cell_size: usize) -> (f32, f32) { 36 + let (x, y) = self.coords(cell_size); 37 + (x + cell_size as f32 / 2.0, y + cell_size as f32 / 2.0) 38 + } 39 + 40 + pub fn distances(&self, other: &Point) -> (usize, usize) { 41 + (self.0.abs_diff(other.0) + 1, self.1.abs_diff(other.1) + 1) 42 + } 43 + } 44 + 45 + impl From<(usize, usize)> for Point { 46 + fn from(value: (usize, usize)) -> Self { 47 + Self(value.0, value.1) 48 + } 49 + } 50 + 51 + impl From<(i32, i32)> for Point { 52 + fn from(value: (i32, i32)) -> Self { 53 + Self(value.0 as usize, value.1 as usize) 54 + } 55 + } 56 + 57 + impl PartialEq<(usize, usize)> for Point { 58 + fn eq(&self, other: &(usize, usize)) -> bool { 59 + self.0 == other.0 && self.1 == other.1 60 + } 61 + } 62 + 63 + impl Eq for Point {} 64 + 65 + impl std::fmt::Display for Point { 66 + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 67 + write!(f, "({}, {})", self.0, self.1) 68 + } 69 + }
+21 -85
src/region.rs
··· 1 - use crate::{Anchor, CenterAnchor}; 1 + use crate::Point; 2 2 use rand::Rng; 3 - 4 3 use wasm_bindgen::prelude::*; 5 4 6 5 #[wasm_bindgen] 7 - #[derive(Debug, Clone, Copy, Default, PartialEq)] 8 - pub struct Point(pub usize, pub usize); 9 - 10 - impl Point { 11 - pub fn translated(&self, dx: i32, dy: i32) -> Self { 12 - Self((self.0 as i32 + dx) as usize, (self.1 as i32 + dy) as usize) 13 - } 14 - 15 - pub fn region(&self) -> Region { 16 - Region { 17 - start: self.clone(), 18 - end: self.clone(), 19 - } 20 - } 21 - } 22 - 23 - impl From<(usize, usize)> for Point { 24 - fn from(value: (usize, usize)) -> Self { 25 - Self(value.0, value.1) 26 - } 27 - } 28 - 29 - impl From<(i32, i32)> for Point { 30 - fn from(value: (i32, i32)) -> Self { 31 - Self(value.0 as usize, value.1 as usize) 32 - } 33 - } 34 - 35 - impl PartialEq<(usize, usize)> for Point { 36 - fn eq(&self, other: &(usize, usize)) -> bool { 37 - self.0 == other.0 && self.1 == other.1 38 - } 39 - } 40 - 41 - #[wasm_bindgen] 42 6 #[derive(Debug, Clone, Default, Copy)] 43 7 pub struct Region { 44 8 pub start: Point, ··· 46 10 } 47 11 48 12 impl Region { 13 + /// iterates from left to right then top to bottom (in a "row-major" order) 49 14 pub fn iter(&self) -> RegionIterator { 50 15 self.into() 51 16 } ··· 74 39 type Item = Point; 75 40 76 41 fn next(&mut self) -> Option<Self::Item> { 77 - if self.current.0 >= self.region.end.0 { 42 + if self.current.0 > self.region.end.0 { 78 43 self.current.0 = self.region.start.0; 79 44 self.current.1 += 1; 80 45 } 81 - if self.current.1 >= self.region.end.1 { 46 + if self.current.1 > self.region.end.1 { 82 47 return None; 83 48 } 84 49 let result = self.current; ··· 123 88 } 124 89 } 125 90 126 - impl From<(&Anchor, &Anchor)> for Region { 127 - fn from(value: (&Anchor, &Anchor)) -> Self { 128 - Region { 129 - start: (value.0 .0, value.0 .1).into(), 130 - end: (value.1 .0, value.1 .1).into(), 131 - } 132 - } 133 - } 134 - 135 - impl From<(&CenterAnchor, &CenterAnchor)> for Region { 136 - fn from(value: (&CenterAnchor, &CenterAnchor)) -> Self { 137 - Region { 138 - start: (value.0 .0, value.0 .1).into(), 139 - end: (value.1 .0 - 1, value.1 .1 - 1).into(), 140 - } 141 - } 142 - } 143 - 144 91 impl std::ops::Sub for Region { 145 92 type Output = (i32, i32); 146 93 ··· 207 154 } 208 155 209 156 pub fn from_topleft(origin: Point, size: (usize, usize)) -> Self { 210 - Self::new( 211 - origin.0, 212 - origin.1, 213 - origin.0 + size.0 + 1, 214 - origin.1 + size.1 + 1, 215 - ) 157 + Self::from(( 158 + origin, 159 + origin.translated_by(Point::from(size).translated(-1, -1)), 160 + )) 216 161 } 217 162 218 163 pub fn from_bottomleft(origin: Point, size: (usize, usize)) -> Self { 219 - Self::new(origin.0, origin.1 - size.1, origin.0 + size.0, origin.1) 164 + Self::from_topleft(origin.translated(0, -(size.1 as i32 - 1)), size) 220 165 } 221 166 222 167 pub fn from_bottomright(origin: Point, size: (usize, usize)) -> Self { 223 - Self::new( 224 - origin.0 - size.0, 225 - origin.1 - size.1, 226 - origin.0 + 1, 227 - origin.1 + 1, 228 - ) 168 + Self::from(( 169 + origin.translated_by(Point::from(size).translated(-1, -1)), 170 + origin, 171 + )) 229 172 } 230 173 231 174 pub fn from_topright(origin: Point, size: (usize, usize)) -> Self { 232 - Self::new( 233 - origin.0 - size.0, 234 - origin.1, 235 - origin.0 + 1, 236 - origin.1 + size.1 + 1, 237 - ) 175 + Self::from_topleft(origin.translated(-(size.0 as i32 - 1), 0), size) 238 176 } 239 177 240 178 pub fn from_center_and_size(center: Point, size: (usize, usize)) -> Self { ··· 293 231 294 232 /// resized is like enlarged, but transforms from the center, by first translating the region by (-dx, -dy) 295 233 pub fn resized(&self, dx: i32, dy: i32) -> Self { 296 - self.translated(-dx, -dy).enlarged(dx, dy) 234 + self.translated(-dx, -dy).enlarged(dx - 1, dy - 1) 297 235 } 298 236 299 237 pub fn x_range(&self) -> std::ops::RangeInclusive<usize> { ··· 330 268 } 331 269 332 270 pub fn width(&self) -> usize { 333 - self.end.0 - self.start.0 271 + self.end.0 - self.start.0 + 1 334 272 } 335 273 336 274 pub fn height(&self) -> usize { 337 - self.end.1 - self.start.1 275 + self.end.1 - self.start.1 + 1 338 276 } 339 277 340 278 // goes from -width to width (inclusive on both ends) ··· 355 293 356 294 impl Containable<Point> for Region { 357 295 fn contains(&self, value: &Point) -> bool { 358 - self.x_range_without_last().contains(&value.0) 359 - && self.y_range_without_last().contains(&value.1) 296 + self.x_range().contains(&value.0) && self.y_range().contains(&value.1) 360 297 } 361 298 } 362 299 363 - impl Containable<Anchor> for Region { 364 - fn contains(&self, anchor: &Anchor) -> bool { 365 - self.x_range_without_last().contains(&(anchor.0 as usize)) 366 - && self.y_range_without_last().contains(&(anchor.1 as usize)) 300 + impl std::fmt::Display for Region { 301 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 302 + write!(f, "[{},{}]", self.start, self.end) 367 303 } 368 304 }
+17 -25
src/web.rs
··· 1 - use std::ptr::NonNull; 2 1 use std::sync::Mutex; 3 2 4 3 use once_cell::sync::Lazy; 5 - use rand::Rng; 6 - use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen}; 4 + use wasm_bindgen::{prelude::wasm_bindgen}; 7 5 use wasm_bindgen::{JsValue, UnwrapThrowExt}; 8 6 9 7 use crate::{ 10 - examples, layer, Anchor, Canvas, CenterAnchor, Color, ColorMapping, Fill, Filter, FilterType, 11 - HatchDirection, Layer, Object, Point, Region, 8 + examples, Canvas, Color, ColorMapping, Fill, Filter, Layer, 9 + Object, Point, Region, 12 10 }; 13 11 14 12 static WEB_CANVAS: Lazy<Mutex<Canvas>> = Lazy::new(|| Mutex::new(Canvas::default_settings())); ··· 39 37 ($($t:tt)*) => (crate::log(&format_args!($($t)*).to_string())) 40 38 } 41 39 42 - pub(crate) use console_log; 43 40 44 41 #[wasm_bindgen] 45 42 pub fn render_image(opacity: f32, color: Color) -> Result<(), JsValue> { 46 - let mut canvas = examples::dna_analysis_machine(); 43 + let mut canvas = examples::title(); 47 44 canvas.colormap = ColorMapping { 48 45 black: "#ffffff".into(), 49 46 white: "#ffffff".into(), ··· 59 56 cyan: "#4fecec".into(), 60 57 }; 61 58 62 - canvas.remove_all_objects_in(&Region::from_topleft(Point(8, 2), (2, 2))); 63 - canvas.remove_all_objects_in(&Point(11, 7).region()); 64 - 65 59 *WEB_CANVAS.lock().unwrap() = canvas; 66 60 render_canvas_at(String::from("body")); 67 61 ··· 69 63 } 70 64 71 65 #[wasm_bindgen] 72 - pub fn map_to_midi_controller() { 73 - 74 - } 66 + pub fn map_to_midi_controller() {} 75 67 76 68 #[wasm_bindgen] 77 69 pub fn render_canvas_into(selector: String) -> () { ··· 251 243 pub fn new_line( 252 244 &self, 253 245 name: &str, 254 - start: Anchor, 255 - end: Anchor, 246 + start: Point, 247 + end: Point, 256 248 thickness: f32, 257 249 color: Color, 258 250 ) -> () { ··· 268 260 pub fn new_curve_outward( 269 261 &self, 270 262 name: &str, 271 - start: Anchor, 272 - end: Anchor, 263 + start: Point, 264 + end: Point, 273 265 thickness: f32, 274 266 color: Color, 275 267 ) -> () { ··· 281 273 pub fn new_curve_inward( 282 274 &self, 283 275 name: &str, 284 - start: Anchor, 285 - end: Anchor, 276 + start: Point, 277 + end: Point, 286 278 thickness: f32, 287 279 color: Color, 288 280 ) -> () { ··· 291 283 Object::CurveInward(start, end, thickness).color(Fill::Solid(color)), 292 284 ) 293 285 } 294 - pub fn new_small_circle(&self, name: &str, center: Anchor, color: Color) -> () { 286 + pub fn new_small_circle(&self, name: &str, center: Point, color: Color) -> () { 295 287 canvas() 296 288 .layer(name) 297 289 .add_object(name, Object::SmallCircle(center).color(Fill::Solid(color))) 298 290 } 299 - pub fn new_dot(&self, name: &str, center: Anchor, color: Color) -> () { 291 + pub fn new_dot(&self, name: &str, center: Point, color: Color) -> () { 300 292 canvas() 301 293 .layer(name) 302 294 .add_object(name, Object::Dot(center).color(Fill::Solid(color))) 303 295 } 304 - pub fn new_big_circle(&self, name: &str, center: CenterAnchor, color: Color) -> () { 296 + pub fn new_big_circle(&self, name: &str, center: Point, color: Color) -> () { 305 297 canvas() 306 298 .layer(name) 307 299 .add_object(name, Object::BigCircle(center).color(Fill::Solid(color))) ··· 309 301 pub fn new_text( 310 302 &self, 311 303 name: &str, 312 - anchor: Anchor, 304 + anchor: Point, 313 305 text: String, 314 306 font_size: f32, 315 307 color: Color, ··· 322 314 pub fn new_rectangle( 323 315 &self, 324 316 name: &str, 325 - topleft: Anchor, 326 - bottomright: Anchor, 317 + topleft: Point, 318 + bottomright: Point, 327 319 color: Color, 328 320 ) -> () { 329 321 canvas().layer(name).add_object(
title.png

This is a binary file and will not be displayed.