diff --git a/Cargo.toml b/Cargo.toml index dba398a5a..1b6989cc3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ image = { version = "0.23", default-features = false, features = ["jpeg", "png"] itoa = "0.4" miniz_oxide = "0.3" pdf-writer = { path = "../pdf-writer" } +ryu = "1.0" ttf-parser = "0.8.2" unicode-xid = "0.2" anyhow = { version = "1", optional = true } diff --git a/src/eval/value.rs b/src/eval/value.rs index 13548c87e..20cc457c8 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -116,8 +116,8 @@ impl Pretty for Value { match self { Value::None => p.push_str("none"), Value::Bool(v) => write!(p, "{}", v).unwrap(), - Value::Int(v) => write!(p, "{}", v).unwrap(), - Value::Float(v) => write!(p, "{}", v).unwrap(), + Value::Int(v) => p.push_str(itoa::Buffer::new().format(*v)), + Value::Float(v) => p.push_str(ryu::Buffer::new().format(*v)), Value::Length(v) => write!(p, "{}", v).unwrap(), Value::Angle(v) => write!(p, "{}", v).unwrap(), Value::Relative(v) => write!(p, "{}", v).unwrap(), @@ -521,9 +521,9 @@ mod tests { test_pretty(false, "false"); test_pretty(12.4, "12.4"); test_pretty(Length::pt(5.5), "5.5pt"); - test_pretty(Angle::deg(90.0), "90deg"); - test_pretty(Relative::ONE / 2.0, "50%"); - test_pretty(Relative::new(0.3) + Length::cm(2.0), "30% + 2cm"); + test_pretty(Angle::deg(90.0), "90.0deg"); + test_pretty(Relative::ONE / 2.0, "50.0%"); + test_pretty(Relative::new(0.3) + Length::cm(2.0), "30.0% + 2.0cm"); test_pretty(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); test_pretty("hello", r#""hello""#); test_pretty(vec![Spanned::zero(Node::Strong)], "[*]"); diff --git a/src/geom/angle.rs b/src/geom/angle.rs index 47541cb8b..07471a026 100644 --- a/src/geom/angle.rs +++ b/src/geom/angle.rs @@ -56,7 +56,16 @@ impl Angle { impl Display for Angle { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}{}", self.to_deg(), AngularUnit::Deg) + // Format with the unit that yields the shortest output, preferring + // degrees when tied. + let mut buf = ryu::Buffer::new(); + let unit = [AngularUnit::Deg, AngularUnit::Rad] + .iter() + .copied() + .min_by_key(|&unit| buf.format(self.to_unit(unit)).len()) + .unwrap(); + + write!(f, "{}{}", buf.format(self.to_unit(unit)), unit) } } diff --git a/src/geom/length.rs b/src/geom/length.rs index bfb1d6681..00803e13c 100644 --- a/src/geom/length.rs +++ b/src/geom/length.rs @@ -99,13 +99,16 @@ impl Length { impl Display for Length { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - // Format small lengths as points and large ones as centimeters. - let (val, unit) = if self.to_pt().abs() < 25.0 { - (self.to_pt(), LengthUnit::Pt) - } else { - (self.to_cm(), LengthUnit::Cm) - }; - write!(f, "{}{}", (val * 100.0).round() / 100.0, unit) + // Format with the unit that yields the shortest output, preferring + // larger units when tied. + let mut buf = ryu::Buffer::new(); + let unit = [LengthUnit::Cm, LengthUnit::Mm, LengthUnit::Pt] + .iter() + .copied() + .min_by_key(|&unit| buf.format(self.to_unit(unit)).len()) + .unwrap(); + + write!(f, "{}{}", buf.format(self.to_unit(unit)), unit) } } @@ -229,8 +232,9 @@ mod tests { #[test] fn test_length_formatting() { - assert_eq!(Length::pt(-28.34).to_string(), "-1cm".to_string()); - assert_eq!(Length::pt(23.0).to_string(), "23pt".to_string()); - assert_eq!(Length::cm(12.728).to_string(), "12.73cm".to_string()); + assert_eq!(Length::pt(23.0).to_string(), "23.0pt"); + assert_eq!(Length::pt(-28.3465).to_string(), "-1.0cm"); + assert_eq!(Length::cm(12.728).to_string(), "12.728cm"); + assert_eq!(Length::cm(4.5).to_string(), "4.5cm"); } } diff --git a/src/geom/relative.rs b/src/geom/relative.rs index d39ead3ae..e91ea6724 100644 --- a/src/geom/relative.rs +++ b/src/geom/relative.rs @@ -37,7 +37,7 @@ impl Relative { impl Display for Relative { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}%", (self.0 * 10000.0).round() / 100.0) + write!(f, "{}%", ryu::Buffer::new().format(100.0 * self.0)) } } diff --git a/src/parse/tests.rs b/src/parse/tests.rs index 8d52c24b5..9a35f5522 100644 --- a/src/parse/tests.rs +++ b/src/parse/tests.rs @@ -3,13 +3,12 @@ use std::fmt::Debug; use super::parse; -use crate::color::RgbaColor; use crate::diag::{Diag, Level, Pass}; -use crate::geom::{AngularUnit, LengthUnit}; +use crate::geom::LengthUnit; use crate::syntax::*; use BinOp::*; -use Expr::{Angle, Bool, Color, Float, Int, Length, Percent}; +use Expr::{Float, Int, Length}; use Node::{Space, Strong}; use UnOp::{Neg, Pos}; @@ -290,43 +289,3 @@ fn test_parse_expressions() { S(6..6, "expected expression"), S(10..10, "expected expression")]); } - -#[test] -fn test_parse_values() { - // Basics. - t!("{_}" Block!(Id("_"))); - t!("{name}" Block!(Id("name"))); - t!("{ke-bab}" Block!(Id("ke-bab"))); - t!("{α}" Block!(Id("α"))); - t!("{none}" Block!(Expr::None)); - t!("{true}" Block!(Bool(true))); - t!("{false}" Block!(Bool(false))); - t!("{1.0e-4}" Block!(Float(1e-4))); - t!("{3.15}" Block!(Float(3.15))); - t!("{50%}" Block!(Percent(50.0))); - t!("{4.5cm}" Block!(Length(4.5, LengthUnit::Cm))); - t!("{12e1pt}" Block!(Length(12e1, LengthUnit::Pt))); - t!("{13rad}" Block!(Angle(13.0, AngularUnit::Rad))); - t!("{45deg}" Block!(Angle(45.0, AngularUnit::Deg))); - - // Strings. - t!(r#"{"hi"}"# Block!(Str("hi"))); - t!(r#"{"a\n[]\"\u{1F680}string"}"# Block!(Str("a\n[]\"🚀string"))); - - // Colors. - t!("{#f7a20500}" Block!(Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0)))); - t!("{#a5}" - nodes: [Block!(Color(RgbaColor::new(0, 0, 0, 0xff)))], - errors: [S(1..4, "invalid color")]); - - // Content. - t!("{[*Hi*]}" Block!(Template![Strong, Text("Hi"), Strong])); - - // Nested blocks. - t!("{{1}}" Block!(Block!(@Int(1)))); - - // Invalid tokens. - t!("{1u}" - nodes: [], - errors: [S(1..3, "expected expression, found invalid token")]); -} diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index b758a8494..795339183 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -54,8 +54,8 @@ impl Pretty for Expr { Self::None => p.push_str("none"), Self::Ident(v) => p.push_str(&v), Self::Bool(v) => write!(p, "{}", v).unwrap(), - Self::Int(v) => write!(p, "{}", v).unwrap(), - Self::Float(v) => write!(p, "{}", v).unwrap(), + Self::Int(v) => p.push_str(itoa::Buffer::new().format(*v)), + Self::Float(v) => p.push_str(ryu::Buffer::new().format(*v)), Self::Length(v, u) => write!(p, "{}{}", v, u).unwrap(), Self::Angle(v, u) => write!(p, "{}{}", v, u).unwrap(), Self::Percent(v) => write!(p, "{}%", v).unwrap(), @@ -370,7 +370,7 @@ mod tests { test_pretty("{true}", "{true}"); test_pretty("{25}", "{25}"); test_pretty("{2.50}", "{2.5}"); - test_pretty("{1e2}", "{100}"); + test_pretty("{1e2}", "{100.0}"); test_pretty("{12pt}", "{12pt}"); test_pretty("{90.0deg}", "{90deg}"); test_pretty("{50%}", "{50%}"); diff --git a/tests/ref/lang/values.png b/tests/ref/lang/values.png new file mode 100644 index 000000000..4205221b7 Binary files /dev/null and b/tests/ref/lang/values.png differ diff --git a/tests/typ/lang/values.typ b/tests/typ/lang/values.typ new file mode 100644 index 000000000..ce41fc433 --- /dev/null +++ b/tests/typ/lang/values.typ @@ -0,0 +1,33 @@ +#let name = "Typst"; +#let ke-bab = "Kebab!"; +#let α = "Alpha"; + +{name} \ +{ke-bab} \ +{α} \ +{none} (empty) \ +{true} \ +{false} \ +{1.0e-4} \ +{3.15} \ +{1e-10} \ +{50.368%} \ +{0.0000012345pt} \ +{4.5cm} \ +{12e1pt} \ +{2.5rad} \ +{45deg} \ +{"hi"} \ +{"a\n[]\"\u{1F680}string"} \ +{#f7a20500} \ +{[*{"Hi"} [f 1]*]} \ +{{1}} + +// Error: 1:1-1:4 unknown variable +{_} \ + +// Error: 1:2-1:5 invalid color +{#a5} + +// Error: 1:2-1:4 expected expression, found invalid token +{1u}