// Copyright 2020-2021 Ian Jackson and contributors to Otter // SPDX-License-Identifier: AGPL-3.0-or-later // There is NO WARRANTY. // This is in this crate for convenience, not because it's to do with // Z coordinates. use crate::prelude::*; pub const SVG_SCALE: f64 = 6.; pub fn timestring_abbreviate<'x>(base: &str, this: &'x str) -> (&'x str, bool) { fn split(s: &str) -> ArrayVec<&str, 3> { s.splitn(3, ' ').collect() } let base3 = split(base); let this3 = split(this); let matches = |i| base3.get(i) == this3.get(i); if_chain! { if matches(0) && matches(2); if let Some(abbrev) = this3.get(1); then { (abbrev, true) } else { (this, false) } } } pub fn raw_angle_transform(compass: u8) -> String { assert!(compass < 8); if compass == 0 { default() } else { format!("rotate({})", -45 * (compass as i16)) } } #[throws(fmt::Error)] pub fn die_cooldown_path(mut w: W, r: f64, remprop: f64) { // This is somewhat entangled with die.svg and // dice/cooldown-template-extractor. // // Editing these might need to be done together. write!(w, "M 0,-{r} A")?; let mut arcto = move |proportion: f64| { let angle = proportion * TAU; let x = r * angle.sin(); let y = -r * angle.cos(); write!(w, " {r},{r} 0 0 1 {x},{y}") // | | `sweep-flag (1 = clockwise) // | `large-arc-flag (see below) // `"angle" (ellipse skew angle) }; // This avoids ever trying to draw an arc segment that is around 180 degrees. // If we did so there could be rounding errors that would mean we might // disagree with the SVG renderer about whether the angle is <=> 180. for split in [0.49, 0.98] { if split >= remprop { break } arcto(split)?; } arcto(remprop)?; } #[test] fn die_cooldown_path_test() { let t80 = |remprop, exp: &str| { let mut got = String::new(); die_cooldown_path(&mut got, 80., remprop).unwrap(); assert_eq!(&got, exp, "\nfor {remprop} {exp}"); }; t80(1./3., "M 0,-80 A 80,80 0 0 1 69.2820323027551,39.999999999999986"); t80(1. , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -10.026658685144373,-79.36917610515822 80,80 0 0 1 -0.000000000000019594348786357652,-80"); t80(0.5 , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 0.000000000000009797174393178826,80"); t80(0.6 , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -47.02282018339784,64.72135954999581"); t80(0.9 , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -47.02282018339787,-64.72135954999578"); t80(0.99 , "M 0,-80 A 80,80 0 0 1 5.023241562345087,79.84213827426173 80,80 0 0 1 -10.026658685144373,-79.36917610515822 80,80 0 0 1 -5.023241562345061,-79.84213827426173"); } pub fn default() -> T { Default::default() } #[macro_export] macro_rules! display_as_debug { {$x:ty $( , $($gen_tt:tt)* )?} => { impl $( $($gen_tt)* )? std::fmt::Display for $x { #[throws(std::fmt::Error)] fn fmt(&self, f: &mut std::fmt::Formatter) { ::fmt(self, f)? } } } } pub use crate::display_as_debug; pub type SvgAttrs = Vec<(HtmlLit,Html)>; pub fn space_table_attrs(table_size: PosC) -> SvgAttrs { let PosC { coords: [x, y] } = table_size; vec![ (Html::lit("viewBox"), hformat!("0 0 {} {}", x, y) ), (Html::lit("width" ), (SVG_SCALE * x).to_html() ), (Html::lit("height" ), (SVG_SCALE * y).to_html() ), ] } pub fn space_rect_attrs(table_size: PosC) -> SvgAttrs { vec![ (Html::lit("width" ), table_size.x().to_html() ), (Html::lit("height"), table_size.y().to_html() ), ] } #[macro_export] macro_rules! if_let { { $($variant:ident)::+ ($binding:pat) = $input:expr; else $($otherwise:tt)* } => { let $binding = match $input { $($variant)::+ (y) => y, _ => { $($otherwise)* }, }; }; { $($variant:ident)::+ ($binding:pat) = $input:expr; $($otherwise:tt)* } => { let $binding = match $input { $($variant)::+ (y) => y, $($otherwise)*, }; }; { $($variant:ident)::+ {$binding:ident} = $input:expr; else $($otherwise:tt)* } => { let $binding = match $input { $($variant)::+ { $binding } => $binding, _ => { $($otherwise)* }, }; }; { $($variant:ident)::+ {$binding:ident} = $input:expr; $($otherwise:tt)* } => { let $binding = match $input { $($variant)::+ { $binding } => $binding, $($otherwise)*, }; }; } #[ext(pub, name=DebugExt)] impl T { fn to_debug(&self) -> String { format!("{:?}", self) } } pub fn dbgc_helper(file: &'static str, line: u32, values: &[(&'static str, &dyn Debug)]) { let buf = (||{ let mut buf = String::new(); write!(buf, "[{}:{}]", file, line)?; for (s, v) in values.iter() { write!(buf, " {}={:?}", s, v)?; } write!(buf, "\n")?; Ok::<_,fmt::Error>(buf) })(); let buf = buf.unwrap_or_else( |e| format!("error formatting for dbgc! {}\n", e)); eprint!("{}", buf); } #[macro_export] macro_rules! dbgc { // NOTE: We cannot use `concat!` to make a static string as a format argument // of `eprintln!` because `file!` could contain a `{` or // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!` // will be malformed. () => { $crate::misc::dbgc_helper(std::file!(), std::line!(), &[]) }; ($val:expr $(,)?) => { // Use of `match` here is intentional because it affects the lifetimes // of temporaries - https://stackoverflow.com/a/48732525/1063961 match $val { tmp => { $crate::misc::dbgc_helper(std::file!(), std::line!(), &[(std::stringify!($val), &tmp)]); tmp } } }; ($($val:expr),+ $(,)?) => { $crate::misc::dbgc_helper(std::file!(), std::line!(), &[$((std::stringify!($val), &$val)),+]) }; }