diff options
Diffstat (limited to 'src')
34 files changed, 340 insertions, 427 deletions
diff --git a/src/eval/array.rs b/src/eval/array.rs index f6adee6d..912aa3c0 100644 --- a/src/eval/array.rs +++ b/src/eval/array.rs @@ -70,7 +70,7 @@ impl Array { /// Clear the array. pub fn clear(&mut self) { - if Rc::strong_count(&mut self.0) == 1 { + if Rc::strong_count(&self.0) == 1 { Rc::make_mut(&mut self.0).clear(); } else { *self = Self::new(); diff --git a/src/eval/dict.rs b/src/eval/dict.rs index e7a46b40..0d7198e1 100644 --- a/src/eval/dict.rs +++ b/src/eval/dict.rs @@ -4,9 +4,9 @@ use std::iter::FromIterator; use std::ops::{Add, AddAssign}; use std::rc::Rc; -use super::{Str, Value}; +use super::Value; use crate::diag::StrResult; -use crate::util::RcExt; +use crate::util::{EcoString, RcExt}; /// Create a new [`Dict`] from key-value pairs. #[allow(unused_macros)] @@ -21,7 +21,7 @@ macro_rules! dict { /// A dictionary from strings to values with clone-on-write value semantics. #[derive(Default, Clone, PartialEq)] -pub struct Dict(Rc<BTreeMap<Str, Value>>); +pub struct Dict(Rc<BTreeMap<EcoString, Value>>); impl Dict { /// Create a new, empty dictionary. @@ -30,7 +30,7 @@ impl Dict { } /// Create a new dictionary from a mapping of strings to values. - pub fn from_map(map: BTreeMap<Str, Value>) -> Self { + pub fn from_map(map: BTreeMap<EcoString, Value>) -> Self { Self(Rc::new(map)) } @@ -45,7 +45,7 @@ impl Dict { } /// Borrow the value the given `key` maps to. - pub fn get(&self, key: Str) -> StrResult<&Value> { + pub fn get(&self, key: EcoString) -> StrResult<&Value> { self.0.get(&key).ok_or_else(|| missing_key(&key)) } @@ -53,18 +53,18 @@ impl Dict { /// /// This inserts the key with [`None`](Value::None) as the value if not /// present so far. - pub fn get_mut(&mut self, key: Str) -> &mut Value { - Rc::make_mut(&mut self.0).entry(key.into()).or_default() + pub fn get_mut(&mut self, key: EcoString) -> &mut Value { + Rc::make_mut(&mut self.0).entry(key).or_default() } /// Insert a mapping from the given `key` to the given `value`. - pub fn insert(&mut self, key: Str, value: Value) { - Rc::make_mut(&mut self.0).insert(key.into(), value); + pub fn insert(&mut self, key: EcoString, value: Value) { + Rc::make_mut(&mut self.0).insert(key, value); } /// Clear the dictionary. pub fn clear(&mut self) { - if Rc::strong_count(&mut self.0) == 1 { + if Rc::strong_count(&self.0) == 1 { Rc::make_mut(&mut self.0).clear(); } else { *self = Self::new(); @@ -72,14 +72,14 @@ impl Dict { } /// Iterate over pairs of references to the contained keys and values. - pub fn iter(&self) -> std::collections::btree_map::Iter<Str, Value> { + pub fn iter(&self) -> std::collections::btree_map::Iter<EcoString, Value> { self.0.iter() } } /// The missing key access error message. #[cold] -fn missing_key(key: &Str) -> String { +fn missing_key(key: &EcoString) -> String { format!("dictionary does not contain key: {:?}", key) } @@ -119,21 +119,21 @@ impl AddAssign for Dict { } } -impl Extend<(Str, Value)> for Dict { - fn extend<T: IntoIterator<Item = (Str, Value)>>(&mut self, iter: T) { +impl Extend<(EcoString, Value)> for Dict { + fn extend<T: IntoIterator<Item = (EcoString, Value)>>(&mut self, iter: T) { Rc::make_mut(&mut self.0).extend(iter); } } -impl FromIterator<(Str, Value)> for Dict { - fn from_iter<T: IntoIterator<Item = (Str, Value)>>(iter: T) -> Self { +impl FromIterator<(EcoString, Value)> for Dict { + fn from_iter<T: IntoIterator<Item = (EcoString, Value)>>(iter: T) -> Self { Self(Rc::new(iter.into_iter().collect())) } } impl IntoIterator for Dict { - type Item = (Str, Value); - type IntoIter = std::collections::btree_map::IntoIter<Str, Value>; + type Item = (EcoString, Value); + type IntoIter = std::collections::btree_map::IntoIter<EcoString, Value>; fn into_iter(self) -> Self::IntoIter { Rc::take(self.0).into_iter() @@ -141,8 +141,8 @@ impl IntoIterator for Dict { } impl<'a> IntoIterator for &'a Dict { - type Item = (&'a Str, &'a Value); - type IntoIter = std::collections::btree_map::Iter<'a, Str, Value>; + type Item = (&'a EcoString, &'a Value); + type IntoIter = std::collections::btree_map::Iter<'a, EcoString, Value>; fn into_iter(self) -> Self::IntoIter { self.iter() diff --git a/src/eval/function.rs b/src/eval/function.rs index cbbc0b36..c83d8b2b 100644 --- a/src/eval/function.rs +++ b/src/eval/function.rs @@ -1,7 +1,7 @@ use std::fmt::{self, Debug, Formatter, Write}; use std::rc::Rc; -use super::{Cast, EvalContext, Str, Value}; +use super::{Cast, EvalContext, Value}; use crate::diag::{At, TypResult}; use crate::syntax::{Span, Spanned}; use crate::util::EcoString; @@ -52,7 +52,10 @@ impl Debug for Function { impl PartialEq for Function { fn eq(&self, other: &Self) -> bool { // We cast to thin pointers for comparison. - Rc::as_ptr(&self.0) as *const () == Rc::as_ptr(&other.0) as *const () + std::ptr::eq( + Rc::as_ptr(&self.0) as *const (), + Rc::as_ptr(&other.0) as *const (), + ) } } @@ -71,7 +74,7 @@ pub struct Arg { /// The span of the whole argument. pub span: Span, /// The name of the argument (`None` for positional arguments). - pub name: Option<Str>, + pub name: Option<EcoString>, /// The value of the argument. pub value: Spanned<Value>, } @@ -173,7 +176,7 @@ impl Args { } /// Reinterpret these arguments as actually being a dictionary key. - pub fn into_key(self) -> TypResult<Str> { + pub fn into_key(self) -> TypResult<EcoString> { self.into_castable("key") } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index fda2184e..1ff497e8 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -5,8 +5,6 @@ mod array; #[macro_use] mod dict; #[macro_use] -mod str; -#[macro_use] mod value; mod capture; mod function; @@ -15,7 +13,6 @@ mod scope; mod template; mod walk; -pub use self::str::*; pub use array::*; pub use capture::*; pub use dict::*; @@ -31,6 +28,8 @@ use std::io; use std::mem; use std::path::PathBuf; +use unicode_segmentation::UnicodeSegmentation; + use crate::diag::{At, Error, StrResult, Trace, Tracepoint, TypResult}; use crate::geom::{Angle, Fractional, Length, Relative}; use crate::image::ImageStore; @@ -38,7 +37,7 @@ use crate::loading::Loader; use crate::source::{SourceId, SourceStore}; use crate::syntax::ast::*; use crate::syntax::{Span, Spanned}; -use crate::util::RefMutExt; +use crate::util::{EcoString, RefMutExt}; use crate::Context; /// Evaluate a parsed source file into a module. @@ -210,7 +209,7 @@ impl Eval for Lit { LitKind::Angle(v, unit) => Value::Angle(Angle::with_unit(v, unit)), LitKind::Percent(v) => Value::Relative(Relative::new(v / 100.0)), LitKind::Fractional(v) => Value::Fractional(Fractional::new(v)), - LitKind::Str(ref v) => Value::Str(v.into()), + LitKind::Str(ref v) => Value::Str(v.clone()), }) } } @@ -239,7 +238,7 @@ impl Eval for DictExpr { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { self.items() - .map(|x| Ok((x.name().take().into(), x.expr().eval(ctx)?))) + .map(|x| Ok((x.name().take(), x.expr().eval(ctx)?))) .collect() } } @@ -401,7 +400,7 @@ impl Eval for CallArgs { CallArg::Named(named) => { items.push(Arg { span, - name: Some(named.name().take().into()), + name: Some(named.name().take()), value: Spanned::new(named.expr().eval(ctx)?, named.expr().span()), }); } @@ -600,7 +599,7 @@ impl Eval for ForExpr { match (key, value, iter) { (None, v, Value::Str(string)) => { - iter!(for (v => value) in string.iter()); + iter!(for (v => value) in string.graphemes(true)); } (None, v, Value::Array(array)) => { iter!(for (v => value) in array.into_iter()); @@ -629,7 +628,7 @@ impl Eval for ImportExpr { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { let path = self.path(); - let resolved = path.eval(ctx)?.cast::<Str>().at(path.span())?; + let resolved = path.eval(ctx)?.cast::<EcoString>().at(path.span())?; let file = ctx.import(&resolved, path.span())?; let module = &ctx.modules[&file]; @@ -659,7 +658,7 @@ impl Eval for IncludeExpr { fn eval(&self, ctx: &mut EvalContext) -> TypResult<Self::Output> { let path = self.path(); - let resolved = path.eval(ctx)?.cast::<Str>().at(path.span())?; + let resolved = path.eval(ctx)?.cast::<EcoString>().at(path.span())?; let file = ctx.import(&resolved, path.span())?; let module = &ctx.modules[&file]; Ok(Value::Template(module.template.clone())) diff --git a/src/eval/ops.rs b/src/eval/ops.rs index 732bfb14..e40fa78d 100644 --- a/src/eval/ops.rs +++ b/src/eval/ops.rs @@ -1,7 +1,9 @@ use std::cmp::Ordering; +use std::convert::TryFrom; use super::Value; use crate::diag::StrResult; +use crate::util::EcoString; use Value::*; /// Bail with a type mismatch error. @@ -150,8 +152,8 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { (Fractional(a), Float(b)) => Fractional(a * b), (Int(a), Fractional(b)) => Fractional(a as f64 * b), - (Str(a), Int(b)) => Str(a.repeat(b)?), - (Int(a), Str(b)) => Str(b.repeat(a)?), + (Str(a), Int(b)) => Str(repeat_str(a, b)?), + (Int(a), Str(b)) => Str(repeat_str(b, a)?), (Array(a), Int(b)) => Array(a.repeat(b)?), (Int(a), Array(b)) => Array(b.repeat(a)?), (Template(a), Int(b)) => Template(a.repeat(b)?), @@ -161,6 +163,16 @@ pub fn mul(lhs: Value, rhs: Value) -> StrResult<Value> { }) } +/// Repeat a string a number of times. +fn repeat_str(string: EcoString, n: i64) -> StrResult<EcoString> { + let n = usize::try_from(n) + .ok() + .and_then(|n| string.len().checked_mul(n).map(|_| n)) + .ok_or_else(|| format!("cannot repeat this string {} times", n))?; + + Ok(string.repeat(n)) +} + /// Compute the quotient of two values. pub fn div(lhs: Value, rhs: Value) -> StrResult<Value> { Ok(match (lhs, rhs) { diff --git a/src/eval/str.rs b/src/eval/str.rs deleted file mode 100644 index 800d1709..00000000 --- a/src/eval/str.rs +++ /dev/null @@ -1,164 +0,0 @@ -use std::borrow::Borrow; -use std::convert::TryFrom; -use std::fmt::{self, Debug, Formatter, Write}; -use std::ops::{Add, AddAssign, Deref}; - -use unicode_segmentation::UnicodeSegmentation; - -use crate::diag::StrResult; -use crate::util::EcoString; - -/// Create a new [`Str`] from a format string. -macro_rules! format_str { - ($($tts:tt)*) => {{ - use std::fmt::Write; - let mut s = $crate::eval::Str::new(); - write!(s, $($tts)*).unwrap(); - s - }}; -} - -/// A string value with inline storage and clone-on-write semantics. -#[derive(Default, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub struct Str(EcoString); - -impl Str { - /// Create a new, empty string. - pub fn new() -> Self { - Self::default() - } - - /// Whether the string is empty. - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - /// The length of the string in bytes. - pub fn len(&self) -> i64 { - self.0.len() as i64 - } - - /// Borrow this as a string slice. - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - /// Return an iterator over the grapheme clusters as strings. - pub fn iter(&self) -> impl Iterator<Item = Str> + '_ { - self.graphemes(true).map(Into::into) - } - - /// Repeat this string `n` times. - pub fn repeat(&self, n: i64) -> StrResult<Self> { - let n = usize::try_from(n) - .ok() - .and_then(|n| self.0.len().checked_mul(n).map(|_| n)) - .ok_or_else(|| format!("cannot repeat this string {} times", n))?; - - Ok(self.0.repeat(n).into()) - } -} - -impl Deref for Str { - type Target = str; - - fn deref(&self) -> &str { - self.0.deref() - } -} - -impl Debug for Str { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_char('"')?; - for c in self.chars() { - match c { - '\\' => f.write_str(r"\\")?, - '"' => f.write_str(r#"\""#)?, - '\n' => f.write_str(r"\n")?, - '\r' => f.write_str(r"\r")?, - '\t' => f.write_str(r"\t")?, - _ => f.write_char(c)?, - } - } - f.write_char('"') - } -} - -impl Add for Str { - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - self += rhs; - self - } -} - -impl AddAssign for Str { - fn add_assign(&mut self, rhs: Self) { - self.0.push_str(rhs.as_str()); - } -} - -impl Write for Str { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.0.write_str(s) - } - - fn write_char(&mut self, c: char) -> fmt::Result { - self.0.write_char(c) - } -} - -impl AsRef<str> for Str { - fn as_ref(&self) -> &str { - self - } -} - -impl Borrow<str> for Str { - fn borrow(&self) -> &str { - self - } -} - -impl From<char> for Str { - fn from(c: char) -> Self { - Self(c.into()) - } -} - -impl From<&str> for Str { - fn from(s: &str) -> Self { - Self(s.into()) - } -} - -impl From<String> for Str { - fn from(s: String) -> Self { - Self(s.into()) - } -} - -impl From<EcoString> for Str { - fn from(s: EcoString) -> Self { - Self(s) - } -} - -impl From<&EcoString> for Str { - fn from(s: &EcoString) -> Self { - Self(s.clone()) - } -} - -impl From<Str> for EcoString { - fn from(s: Str) -> Self { - s.0 - } -} - -impl From<&Str> for EcoString { - fn from(s: &Str) -> Self { - s.0.clone() - } -} diff --git a/src/eval/template.rs b/src/eval/template.rs index 2622a1f0..18104638 100644 --- a/src/eval/template.rs +++ b/src/eval/template.rs @@ -5,7 +5,6 @@ use std::mem; use std::ops::{Add, AddAssign}; use std::rc::Rc; -use super::Str; use crate::diag::StrResult; use crate::geom::{Align, Dir, GenAxis, Length, Linear, Sides, Size}; use crate::layout::{BlockLevel, BlockNode, InlineLevel, InlineNode, PageNode}; @@ -214,20 +213,20 @@ impl AddAssign for Template { } } -impl Add<Str> for Template { +impl Add<EcoString> for Template { type Output = Self; - fn add(mut self, rhs: Str) -> Self::Output { - Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs.into())); + fn add(mut self, rhs: EcoString) -> Self::Output { + Rc::make_mut(&mut self.0).push(TemplateNode::Text(rhs)); self } } -impl Add<Template> for Str { +impl Add<Template> for EcoString { type Output = Template; fn add(self, mut rhs: Template) -> Self::Output { - Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self.into())); + Rc::make_mut(&mut rhs.0).insert(0, TemplateNode::Text(self)); rhs } } @@ -491,7 +490,7 @@ impl ParBuilder { self.children.last_mut() { if prev_align == curr_align && Rc::ptr_eq(prev_props, curr_props) { - prev_text.push_str(&curr_text); + prev_text.push_str(curr_text); return; } } diff --git a/src/eval/value.rs b/src/eval/value.rs index 804f8d55..0fcc4bfc 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -3,7 +3,7 @@ use std::cmp::Ordering; use std::fmt::{self, Debug, Formatter}; use std::rc::Rc; -use super::{ops, Array, Dict, Function, Str, Template}; +use super::{ops, Array, Dict, Function, Template}; use crate::diag::StrResult; use crate::geom::{Angle, Color, Fractional, Length, Linear, Relative, RgbaColor}; use crate::syntax::Spanned; @@ -35,7 +35,7 @@ pub enum Value { /// A color value: `#f79143ff`. Color(Color), /// A string: `"string"`. - Str(Str), + Str(EcoString), /// An array of values: `(1, "hi", 12cm)`. Array(Array), /// A dictionary value: `(color: #f79143, pattern: dashed)`. @@ -63,7 +63,7 @@ impl Value { Self::Linear(_) => Linear::TYPE_NAME, Self::Fractional(_) => Fractional::TYPE_NAME, Self::Color(_) => Color::TYPE_NAME, - Self::Str(_) => Str::TYPE_NAME, + Self::Str(_) => EcoString::TYPE_NAME, Self::Array(_) => Array::TYPE_NAME, Self::Dict(_) => Dict::TYPE_NAME, Self::Template(_) => Template::TYPE_NAME, @@ -81,8 +81,8 @@ impl Value { } /// Return the debug representation of the value. - pub fn repr(&self) -> Str { - format_str!("{:?}", self) + pub fn repr(&self) -> EcoString { + format_eco!("{:?}", self) } /// Join the value with another value. @@ -163,17 +163,12 @@ impl From<String> for Value { } } -impl From<EcoString> for Value { - fn from(v: EcoString) -> Self { - Self::Str(v.into()) - } -} - impl From<Dynamic> for Value { fn from(v: Dynamic) -> Self { Self::Dyn(v) } } + /// A dynamic value. #[derive(Clone)] pub struct Dynamic(Rc<dyn Bounds>); @@ -338,7 +333,8 @@ macro_rules! dynamic { } castable! { - $type: <Self as $crate::eval::Type>::TYPE_NAME, + $type, + Expected: <Self as $crate::eval::Type>::TYPE_NAME, $($tts)* @this: Self => this.clone(), } @@ -354,8 +350,8 @@ macro_rules! dynamic { /// Make a type castable from a value. macro_rules! castable { ( - $type:ty: - $expected:expr, + $type:ty, + Expected: $expected:expr, $($pattern:pat => $out:expr,)* $(@$dyn_in:ident: $dyn_type:ty => $dyn_out:expr,)* ) => { @@ -398,7 +394,7 @@ primitive! { Relative: "relative", Relative } primitive! { Linear: "linear", Linear, Length(v) => v.into(), Relative(v) => v.into() } primitive! { Fractional: "fractional", Fractional } primitive! { Color: "color", Color } -primitive! { Str: "string", Str } +primitive! { EcoString: "string", Str } primitive! { Array: "array", Array } primitive! { Dict: "dictionary", Dict } primitive! { Template: "template", Template } diff --git a/src/eval/walk.rs b/src/eval/walk.rs index aab32f40..6d5df5ba 100644 --- a/src/eval/walk.rs +++ b/src/eval/walk.rs @@ -1,12 +1,12 @@ use std::rc::Rc; -use super::{Eval, EvalContext, Str, Template, Value}; +use super::{Eval, EvalContext, Template, Value}; use crate::diag::TypResult; use crate::geom::Spec; use crate::layout::BlockLevel; use crate::library::{GridNode, ParChild, ParNode, TrackSizing}; use crate::syntax::ast::*; -use crate::util::BoolExt; +use crate::util::{BoolExt, EcoString}; /// Walk markup, filling the currently built template. pub trait Walk { @@ -39,8 +39,8 @@ impl Walk for MarkupNode { Self::Enum(enum_) => enum_.walk(ctx)?, Self::Expr(expr) => match expr.eval(ctx)? { Value::None => {} - Value::Int(v) => ctx.template.text(format_str!("{}", v)), - Value::Float(v) => ctx.template.text(format_str!("{}", v)), + Value::Int(v) => ctx.template.text(format_eco!("{}", v)), + Value::Float(v) => ctx.template.text(format_eco!("{}", v)), Value::Str(v) => ctx.template.text(v), Value::Template(v) => ctx.template += v, // For values which can't be shown "naturally", we print the @@ -108,7 +108,7 @@ impl Walk for HeadingNode { impl Walk for ListNode { fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { let body = self.body().eval(ctx)?; - walk_item(ctx, Str::from('•'), body); + walk_item(ctx, EcoString::from('•'), body); Ok(()) } } @@ -116,19 +116,19 @@ impl Walk for ListNode { impl Walk for EnumNode { fn walk(&self, ctx: &mut EvalContext) -> TypResult<()> { let body = self.body().eval(ctx)?; - let label = format_str!("{}.", self.number().unwrap_or(1)); + let label = format_eco!("{}.", self.number().unwrap_or(1)); walk_item(ctx, label, body); Ok(()) } } -fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) { +fn walk_item(ctx: &mut EvalContext, label: EcoString, body: Template) { ctx.template += Template::from_block(move |style| { let label = ParNode { dir: style.dir, leading: style.leading(), children: vec![ParChild::Text( - (&label).into(), + label.clone(), style.aligns.inline, Rc::clone(&style.text), )], @@ -138,7 +138,7 @@ fn walk_item(ctx: &mut EvalContext, label: Str, body: Template) { GridNode { tracks: Spec::new(vec![TrackSizing::Auto; 2], vec![]), gutter: Spec::new(vec![TrackSizing::Linear(spacing.into())], vec![]), - children: vec![label.pack(), body.to_stack(&style).pack()], + children: vec![label.pack(), body.to_stack(style).pack()], } }); } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 5649d552..d517aadf 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -100,7 +100,7 @@ impl<'a> PdfExporter<'a> { let mut resources = pages.resources(); let mut fonts = resources.fonts(); for (refs, f) in self.refs.fonts().zip(self.font_map.pdf_indices()) { - let name = format!("F{}", f); + let name = format_eco!("F{}", f); fonts.pair(Name(name.as_bytes()), refs.type0_font); } @@ -108,7 +108,7 @@ impl<'a> PdfExporter<'a> { let mut images = resources.x_objects(); for (id, im) in self.refs.images().zip(self.image_map.pdf_indices()) { - let name = format!("Im{}", im); + let name = format_eco!("Im{}", im); images.pair(Name(name.as_bytes()), id); } @@ -153,7 +153,7 @@ impl<'a> PdfExporter<'a> { fn write_pages(&mut self) { for (id, page) in self.refs.contents().zip(self.frames) { - self.write_page(id, &page); + self.write_page(id, page); } } @@ -199,7 +199,7 @@ impl<'a> PdfExporter<'a> { face_id = Some(text.face_id); size = text.size; - let name = format!("F{}", self.font_map.map(text.face_id)); + let name = format_eco!("F{}", self.font_map.map(text.face_id)); content.set_font(Name(name.as_bytes()), size.to_pt() as f32); } @@ -541,7 +541,7 @@ fn encode_image(img: &Image) -> ImageResult<(Vec<u8>, Filter, ColorSpace)> { // 8-bit gray PNG. (ImageFormat::Png, DynamicImage::ImageLuma8(luma)) => { - let data = deflate(&luma.as_raw()); + let data = deflate(luma.as_raw()); (data, Filter::FlateDecode, ColorSpace::DeviceGray) } diff --git a/src/export/subset.rs b/src/export/subset.rs index 4b6fef8e..b58e042e 100644 --- a/src/export/subset.rs +++ b/src/export/subset.rs @@ -36,7 +36,7 @@ struct Subsetter<'a> { impl<'a> Subsetter<'a> { /// Parse the font header and create a new subsetter. fn new(data: &'a [u8], index: u32, glyphs: &'a HashSet<u16>) -> Option<Self> { - let mut s = Stream::new(&data); + let mut s = Stream::new(data); let mut magic = s.read::<Magic>()?; if magic == Magic::Collection { @@ -46,7 +46,7 @@ impl<'a> Subsetter<'a> { let offsets = s.read_array32::<Offset32>(num_faces)?; let offset = offsets.get(index)?.to_usize(); - s = Stream::new_at(&data, offset)?; + s = Stream::new_at(data, offset)?; magic = s.read::<Magic>()?; if magic == Magic::Collection { return None; @@ -117,7 +117,7 @@ impl<'a> Subsetter<'a> { let len = data.len(); w.write(TableRecord { tag: *tag, - checksum: checksum(&data), + checksum: checksum(data), offset: offset as u32, length: len as u32, }); diff --git a/src/font.rs b/src/font.rs index 6b76eda6..fb93d5c9 100644 --- a/src/font.rs +++ b/src/font.rs @@ -317,30 +317,6 @@ pub enum VerticalFontMetric { Linear(Linear), } -/// A generic or named font family. -#[derive(Clone, Eq, PartialEq, Hash)] -pub enum FontFamily { - /// A family that has "serifs", small strokes attached to letters. - Serif, - /// A family in which glyphs do not have "serifs", small attached strokes. - SansSerif, - /// A family in which (almost) all glyphs are of equal width. - Monospace, - /// A specific family with a name. - Named(String), -} - -impl Debug for FontFamily { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.pad(match self { - Self::Serif => "serif", - Self::SansSerif => "sans-serif", - Self::Monospace => "monospace", - Self::Named(s) => s, - }) - } -} - /// Properties of a single font face. #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct FaceInfo { @@ -28,6 +28,8 @@ //! [PDF]: export::pdf #[macro_use] +pub mod util; +#[macro_use] pub mod diag; #[macro_use] pub mod eval; @@ -43,7 +45,6 @@ pub mod parse; pub mod source; pub mod style; pub mod syntax; -pub mod util; use std::rc::Rc; @@ -182,7 +183,7 @@ impl ContextBuilder { loader, #[cfg(feature = "layout-cache")] layouts: LayoutCache::new(self.policy, self.max_size), - std: self.std.unwrap_or(library::new()), + std: self.std.unwrap_or_else(library::new), style: self.style.unwrap_or_default(), } } diff --git a/src/library/deco.rs b/src/library/deco.rs index 18ca2bb1..6ef5a97b 100644 --- a/src/library/deco.rs +++ b/src/library/deco.rs @@ -36,14 +36,14 @@ fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> { /// `link`: Typeset text as a link. pub fn link(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let url = args.expect::<Str>("url")?; + let url = args.expect::<EcoString>("url")?; let body = args.find().unwrap_or_else(|| { let mut template = Template::new(); template.text(url.trim_start_matches("mailto:").trim_start_matches("tel:")); template }); - Ok(Value::Template(body.decorate(Decoration::Link(url.into())))) + Ok(Value::Template(body.decorate(Decoration::Link(url)))) } /// A decoration for a frame. diff --git a/src/library/grid.rs b/src/library/grid.rs index af486496..c7e6b8e9 100644 --- a/src/library/grid.rs +++ b/src/library/grid.rs @@ -3,7 +3,8 @@ use super::prelude::*; /// `grid`: Arrange children into a grid. pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { castable! { - Vec<TrackSizing>: "integer or (auto, linear, fractional, or array thereof)", + Vec<TrackSizing>, + Expected: "integer or (auto, linear, fractional, or array thereof)", Value::Auto => vec![TrackSizing::Auto], Value::Length(v) => vec![TrackSizing::Linear(v.into())], Value::Relative(v) => vec![TrackSizing::Linear(v.into())], @@ -17,7 +18,8 @@ pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - TrackSizing: "auto, linear, or fractional", + TrackSizing, + Expected: "auto, linear, or fractional", Value::Auto => Self::Auto, Value::Length(v) => Self::Linear(v.into()), Value::Relative(v) => Self::Linear(v.into()), @@ -43,10 +45,7 @@ pub fn grid(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { GridNode { tracks: tracks.clone(), gutter: gutter.clone(), - children: children - .iter() - .map(|child| child.to_stack(&style).pack()) - .collect(), + children: children.iter().map(|child| child.to_stack(style).pack()).collect(), } }))) } diff --git a/src/library/image.rs b/src/library/image.rs index c2273502..b51e4e70 100644 --- a/src/library/image.rs +++ b/src/library/image.rs @@ -6,7 +6,7 @@ use crate::image::ImageId; /// `image`: An image. pub fn image(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let path = args.expect::<Spanned<Str>>("path to image file")?; + let path = args.expect::<Spanned<EcoString>>("path to image file")?; let width = args.named("width")?; let height = args.named("height")?; diff --git a/src/library/mod.rs b/src/library/mod.rs index b0a42e83..e6cb0d9d 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -23,12 +23,12 @@ mod prelude { pub use std::rc::Rc; pub use crate::diag::{At, TypResult}; - pub use crate::eval::{Args, EvalContext, Str, Template, Value}; + pub use crate::eval::{Args, EvalContext, Template, Value}; pub use crate::frame::*; pub use crate::geom::*; pub use crate::layout::*; pub use crate::syntax::{Span, Spanned}; - pub use crate::util::OptionExt; + pub use crate::util::{EcoString, OptionExt}; } pub use self::image::*; @@ -47,8 +47,8 @@ pub use transform::*; pub use utility::*; use crate::eval::{Scope, Value}; -use crate::font::FontFamily; use crate::geom::*; +use crate::style::FontFamily; /// Construct a scope containing all standard library definitions. pub fn new() -> Scope { diff --git a/src/library/pad.rs b/src/library/pad.rs index 6457f603..8c3c0f53 100644 --- a/src/library/pad.rs +++ b/src/library/pad.rs @@ -19,7 +19,7 @@ pub fn pad(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { Ok(Value::Template(Template::from_block(move |style| { PadNode { padding, - child: body.to_stack(&style).pack(), + child: body.to_stack(style).pack(), } }))) } diff --git a/src/library/page.rs b/src/library/page.rs index b31a32a3..16e6283d 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -4,7 +4,8 @@ use crate::style::{Paper, PaperClass}; /// `page`: Configure pages. pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { castable! { - Paper: "string", + Paper, + Expected: "string", Value::Str(string) => Paper::from_name(&string).ok_or("unknown paper")?, } diff --git a/src/library/par.rs b/src/library/par.rs index 3330eedf..eeb46dd8 100644 --- a/src/library/par.rs +++ b/src/library/par.rs @@ -34,7 +34,7 @@ pub fn par(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { /// `lang`: Configure the language. pub fn lang(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - let iso = args.find::<Str>(); + let iso = args.find::<EcoString>(); let dir = if let Some(dir) = args.named::<Spanned<Dir>>("dir")? { if dir.v.axis() == SpecAxis::Horizontal { Some(dir.v) diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 23328c86..26f1bee1 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -24,7 +24,8 @@ pub enum Spacing { } castable! { - Spacing: "linear or fractional", + Spacing, + Expected: "linear or fractional", Value::Length(v) => Self::Linear(v.into()), Value::Relative(v) => Self::Linear(v.into()), Value::Linear(v) => Self::Linear(v), diff --git a/src/library/stack.rs b/src/library/stack.rs index 80ed507b..ffd9b44f 100644 --- a/src/library/stack.rs +++ b/src/library/stack.rs @@ -11,7 +11,8 @@ pub fn stack(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - Child: "linear, fractional or template", + Child, + Expected: "linear, fractional or template", Value::Length(v) => Self::Spacing(Spacing::Linear(v.into())), Value::Relative(v) => Self::Spacing(Spacing::Linear(v.into())), Value::Linear(v) => Self::Spacing(Spacing::Linear(v)), diff --git a/src/library/text.rs b/src/library/text.rs index 4cf94bde..914122a1 100644 --- a/src/library/text.rs +++ b/src/library/text.rs @@ -7,48 +7,42 @@ use ttf_parser::Tag; use super::prelude::*; use crate::font::{ - Face, FaceId, FontFamily, FontStore, FontStretch, FontStyle, FontVariant, FontWeight, + Face, FaceId, FontStore, FontStretch, FontStyle, FontVariant, FontWeight, VerticalFontMetric, }; use crate::geom::{Dir, Em, Length, Point, Size}; use crate::style::{ - FontFeatures, NumberPosition, NumberType, NumberWidth, Style, TextStyle, + FontFamily, FontFeatures, NumberPosition, NumberType, NumberWidth, Style, + StylisticSet, TextStyle, }; -use crate::util::SliceExt; +use crate::util::{EcoString, SliceExt}; /// `font`: Configure the font. pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - struct FontDef(Rc<Vec<FontFamily>>); - struct FamilyDef(Rc<Vec<String>>); - struct FeatureList(Vec<(Tag, u32)>); - struct StylisticSet(Option<u8>); - castable! { - FontDef: "font family or array of font families", - Value::Str(string) => Self(Rc::new(vec![FontFamily::Named(string.to_lowercase())])), - Value::Array(values) => Self(Rc::new( - values - .into_iter() - .filter_map(|v| v.cast().ok()) - .collect() - )), - @family: FontFamily => Self(Rc::new(vec![family.clone()])), + Vec<FontFamily>, + Expected: "string, generic family or array thereof", + Value::Str(string) => vec![FontFamily::Named(string.to_lowercase())], + Value::Array(values) => { + values.into_iter().filter_map(|v| v.cast().ok()).collect() + }, + @family: FontFamily => vec![family.clone()], } castable! { - FamilyDef: "string or array of strings", - Value::Str(string) => Self(Rc::new(vec![string.to_lowercase()])), - Value::Array(values) => Self(Rc::new( - values - .into_iter() - .filter_map(|v| v.cast().ok()) - .map(|string: Str| string.to_lowercase()) - .collect() - )), + Vec<EcoString>, + Expected: "string or array of strings", + Value::Str(string) => vec![string.to_lowercase()], + Value::Array(values) => values + .into_iter() + .filter_map(|v| v.cast().ok()) + .map(|string: EcoString| string.to_lowercase()) + .collect(), } castable! { - FontStyle: "string", + FontStyle, + Expected: "string", Value::Str(string) => match string.as_str() { "normal" => Self::Normal, "italic" => Self::Italic, @@ -58,7 +52,8 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - FontWeight: "integer or string", + FontWeight, + Expected: "integer or string", Value::Int(v) => v.try_into().map_or(Self::BLACK, Self::from_number), Value::Str(string) => match string.as_str() { "thin" => Self::THIN, @@ -75,12 +70,14 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - FontStretch: "relative", + FontStretch, + Expected: "relative", Value::Relative(v) => Self::from_ratio(v.get() as f32), } castable! { - VerticalFontMetric: "linear or string", + VerticalFontMetric, + Expected: "linear or string", Value::Length(v) => Self::Linear(v.into()), Value::Relative(v) => Self::Linear(v.into()), Value::Linear(v) => Self::Linear(v), @@ -95,7 +92,8 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - StylisticSet: "none or integer", + StylisticSet, + Expected: "none or integer", Value::None => Self(None), Value::Int(v) => match v { 1 ..= 20 => Self(Some(v as u8)), @@ -104,7 +102,8 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - NumberType: "auto or string", + NumberType, + Expected: "auto or string", Value::Auto => Self::Auto, Value::Str(string) => match string.as_str() { "lining" => Self::Lining, @@ -114,7 +113,8 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - NumberWidth: "auto or string", + NumberWidth, + Expected: "auto or string", Value::Auto => Self::Auto, Value::Str(string) => match string.as_str() { "proportional" => Self::Proportional, @@ -124,7 +124,8 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - NumberPosition: "string", + NumberPosition, + Expected: "string", Value::Str(string) => match string.as_str() { "normal" => Self::Normal, "subscript" => Self::Subscript, @@ -134,44 +135,39 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { } castable! { - FeatureList: "array of strings or dictionary mapping tags to integers", - Value::Array(values) => Self( - values - .into_iter() - .filter_map(|v| v.cast().ok()) - .map(|string: Str| (Tag::from_bytes_lossy(string.as_bytes()), 1)) - .collect() - ), - Value::Dict(values) => Self( - values - .into_iter() - .filter_map(|(k, v)| { - let tag = Tag::from_bytes_lossy(k.as_bytes()); - let num = v.cast::<i64>().ok()?.try_into().ok()?; - Some((tag, num)) - }) - .collect() - ), + Vec<(Tag, u32)>, + Expected: "array of strings or dictionary mapping tags to integers", + Value::Array(values) => values + .into_iter() + .filter_map(|v| v.cast().ok()) + .map(|string: EcoString| (Tag::from_bytes_lossy(string.as_bytes()), 1)) + .collect(), + Value::Dict(values) => values + .into_iter() + .filter_map(|(k, v)| { + let tag = Tag::from_bytes_lossy(k.as_bytes()); + let num = v.cast::<i64>().ok()?.try_into().ok()?; + Some((tag, num)) + }) + .collect(), } let list = args.named("family")?.or_else(|| { let families: Vec<_> = args.all().collect(); - (!families.is_empty()).then(|| FontDef(Rc::new(families))) + (!families.is_empty()).then(|| families) }); let serif = args.named("serif")?; let sans_serif = args.named("sans-serif")?; let monospace = args.named("monospace")?; let fallback = args.named("fallback")?; - - let size = args.named::<Linear>("size")?.or_else(|| args.find()); let style = args.named("style")?; let weight = args.named("weight")?; let stretch = args.named("stretch")?; - let fill = args.named("fill")?.or_else(|| args.find()); + let size = args.named::<Linear>("size")?.or_else(|| args.find()); let top_edge = args.named("top-edge")?; let bottom_edge = args.named("bottom-edge")?; - + let fill = args.named("fill")?.or_else(|| args.find()); let kerning = args.named("kerning")?; let smallcaps = args.named("smallcaps")?; let alternates = args.named("alternates")?; @@ -199,22 +195,6 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let f = move |style_: &mut Style| { let text = style_.text_mut(); - if let Some(FontDef(list)) = &list { - text.families_mut().list = list.clone(); - } - - if let Some(FamilyDef(serif)) = &serif { - text.families_mut().serif = serif.clone(); - } - - if let Some(FamilyDef(sans_serif)) = &sans_serif { - text.families_mut().sans_serif = sans_serif.clone(); - } - - if let Some(FamilyDef(monospace)) = &monospace { - text.families_mut().monospace = monospace.clone(); - } - if let Some(size) = size { text.size = size.resolve(text.size); } @@ -223,16 +203,21 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { text.fill = Paint::Color(fill); } - set!(text.fallback => fallback); set!(text.variant.style => style); set!(text.variant.weight => weight); set!(text.variant.stretch => stretch); set!(text.top_edge => top_edge); set!(text.bottom_edge => bottom_edge); + set!(text.fallback => fallback); + set!(text.families_mut().list => list.clone()); + set!(text.families_mut().serif => serif.clone()); + set!(text.families_mut().sans_serif => sans_serif.clone()); + set!(text.families_mut().monospace => monospace.clone()); set!(text.features_mut().kerning => kerning); set!(text.features_mut().smallcaps => smallcaps); set!(text.features_mut().alternates => alternates); + set!(text.features_mut().stylistic_set => stylistic_set); set!(text.features_mut().ligatures.standard => ligatures); set!(text.features_mut().ligatures.discretionary => discretionary_ligatures); set!(text.features_mut().ligatures.historical => historical_ligatures); @@ -241,14 +226,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> { set!(text.features_mut().numbers.position => number_position); set!(text.features_mut().numbers.slashed_zero => slashed_zero); set!(text.features_mut().numbers.fractions => fractions); - - if let Some(StylisticSet(stylistic_set)) = stylistic_set { - text.features_mut().stylistic_set = stylistic_set; - } - - if let Some(FeatureList(features)) = &features { - text.features_mut().raw = features.clone(); - } + set!(text.features_mut().raw => features.clone()); }; Ok(if let Some(body) = body { @@ -638,12 +616,10 @@ fn tags(features: &FontFeatures) -> Vec<Feature> { feat(b"salt", 1); } - let set_tag; - if let Some(set) = features.stylistic_set { - if matches!(set, 1 ..= 20) { - set_tag = [b's', b's', b'0' + set / 10, b'0' + set % 10]; - feat(&set_tag, 1); - } + let storage; + if let StylisticSet(Some(set @ 1 ..= 20)) = features.stylistic_set { + storage = [b's', b's', b'0' + set / 10, b'0' + set % 10]; + feat(&storage, 1); } if !features.ligatures.standard { diff --git a/src/library/utility.rs b/src/library/utility.rs index 69ae235d..6d15a823 100644 --- a/src/library/utility.rs +++ b/src/library/utility.rs @@ -77,8 +77,8 @@ pub fn float(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { pub fn str(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let Spanned { v, span } = args.expect("value")?; Ok(Value::Str(match v { - Value::Int(v) => format_str!("{}", v), - Value::Float(v) => format_str!("{}", v), + Value::Int(v) => format_eco!("{}", v), + Value::Float(v) => format_eco!("{}", v), Value::Str(v) => v, v => bail!(span, "cannot convert {} to string", v.type_name()), })) @@ -87,7 +87,7 @@ pub fn str(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { /// `rgb`: Create an RGB(A) color. pub fn rgb(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { Ok(Value::Color(Color::Rgba( - if let Some(string) = args.find::<Spanned<Str>>() { + if let Some(string) = args.find::<Spanned<EcoString>>() { match RgbaColor::from_str(&string.v) { Ok(color) => color, Err(_) => bail!(string.span, "invalid hex string"), @@ -98,7 +98,7 @@ pub fn rgb(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let b = args.expect("blue component")?; let a = args.eat()?.unwrap_or(Spanned::new(1.0, Span::detached())); let f = |Spanned { v, span }: Spanned<f64>| { - if 0.0 <= v && v <= 1.0 { + if (0.0 ..= 1.0).contains(&v) { Ok((v * 255.0).round() as u8) } else { bail!(span, "value must be between 0.0 and 1.0"); @@ -182,19 +182,19 @@ pub fn range(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { /// `lower`: Convert a string to lowercase. pub fn lower(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - Ok(args.expect::<Str>("string")?.to_lowercase().into()) + Ok(args.expect::<EcoString>("string")?.to_lowercase().into()) } /// `upper`: Convert a string to uppercase. pub fn upper(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { - Ok(args.expect::<Str>("string")?.to_uppercase().into()) + Ok(args.expect::<EcoString>("string")?.to_uppercase().into()) } /// `len`: The length of a string, an array or a dictionary. pub fn len(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> { let Spanned { v, span } = args.expect("collection")?; Ok(Value::Int(match v { - Value::Str(v) => v.len(), + Value::Str(v) => v.len() as i64, Value::Array(v) => v.len(), Value::Dict(v) => v.len(), v => bail!( diff --git a/src/loading/fs.rs b/src/loading/fs.rs index ed544ba7..12996a69 100644 --- a/src/loading/fs.rs +++ b/src/loading/fs.rs @@ -119,7 +119,7 @@ impl FsLoader { let path = path.strip_prefix(".").unwrap_or(path); if let Ok(file) = File::open(path) { if let Ok(mmap) = unsafe { Mmap::map(&file) } { - self.faces.extend(FaceInfo::parse(&path, &mmap)); + self.faces.extend(FaceInfo::parse(path, &mmap)); } } } diff --git a/src/parse/parser.rs b/src/parse/parser.rs index 1c4c2a5c..6598b1f2 100644 --- a/src/parse/parser.rs +++ b/src/parse/parser.rs @@ -242,7 +242,7 @@ impl<'s> Parser<'s> { self.eat(); rescan = false; } else if required { - self.push_error(format!("expected {}", end)); + self.push_error(format_eco!("expected {}", end)); } } @@ -327,8 +327,8 @@ impl Parser<'_> { pub fn unexpected(&mut self) { match self.peek() { Some(found) => { - let msg = format!("unexpected {}", found); - let error = NodeKind::Error(ErrorPos::Full, msg.into()); + let msg = format_eco!("unexpected {}", found); + let error = NodeKind::Error(ErrorPos::Full, msg); self.perform(error, Self::eat); } None => self.push_error("unexpected end of file"), @@ -339,8 +339,8 @@ impl Parser<'_> { pub fn expected(&mut self, thing: &str) { match self.peek() { Some(found) => { - let msg = format!("expected {}, found {}", thing, found); - let error = NodeKind::Error(ErrorPos::Full, msg.into()); + let msg = format_eco!("expected {}, found {}", thing, found); + let error = NodeKind::Error(ErrorPos::Full, msg); self.perform(error, Self::eat); } None => self.expected_at(thing), @@ -400,8 +400,8 @@ impl Marker { /// Insert an error message that `what` was expected at the marker position. pub fn expected(self, p: &mut Parser, what: &str) { - let msg = format!("expected {}", what); - let error = NodeKind::Error(ErrorPos::Full, msg.into()); + let msg = format_eco!("expected {}", what); + let error = NodeKind::Error(ErrorPos::Full, msg); p.children.insert(self.0, GreenData::new(error, 0).into()); } diff --git a/src/parse/scanner.rs b/src/parse/scanner.rs index ea06a2e0..c735be40 100644 --- a/src/parse/scanner.rs +++ b/src/parse/scanner.rs @@ -119,7 +119,7 @@ impl<'s> Scanner<'s> { /// The full source string. #[inline] pub fn src(&self) -> &'s str { - &self.src + self.src } /// Slice out part of the source string. diff --git a/src/parse/tokens.rs b/src/parse/tokens.rs index 96dfd9d1..f80d345e 100644 --- a/src/parse/tokens.rs +++ b/src/parse/tokens.rs @@ -239,7 +239,7 @@ impl<'s> Tokens<'s> { self.s.eat_assert('{'); let sequence = self.s.eat_while(|c| c.is_ascii_alphanumeric()); if self.s.eat_if('}') { - if let Some(c) = resolve_hex(&sequence) { + if let Some(c) = resolve_hex(sequence) { NodeKind::UnicodeEscape(c) } else { NodeKind::Error( @@ -270,7 +270,7 @@ impl<'s> Tokens<'s> { None => NodeKind::Ident(read.into()), } } else { - NodeKind::Text("#".into()) + NodeKind::Text('#'.into()) } } @@ -284,7 +284,7 @@ impl<'s> Tokens<'s> { } else if self.s.check_or(true, char::is_whitespace) { NodeKind::Minus } else { - NodeKind::Text("-".into()) + NodeKind::Text('-'.into()) } } @@ -340,7 +340,7 @@ impl<'s> Tokens<'s> { NodeKind::Raw(Rc::new(resolve_raw( column, backticks, - self.s.get(start .. end).into(), + self.s.get(start .. end), ))) } else { let remaining = backticks - found; @@ -349,11 +349,10 @@ impl<'s> Tokens<'s> { NodeKind::Error( ErrorPos::End, if found == 0 { - format!("expected {} {}", remaining, noun) + format_eco!("expected {} {}", remaining, noun) } else { - format!("expected {} more {}", remaining, noun) - } - .into(), + format_eco!("expected {} more {}", remaining, noun) + }, ) } } @@ -397,11 +396,10 @@ impl<'s> Tokens<'s> { NodeKind::Error( ErrorPos::End, if !display || (!escaped && dollar) { - "expected closing dollar sign" + "expected closing dollar sign".into() } else { - "expected closing bracket and dollar sign" - } - .into(), + "expected closing bracket and dollar sign".into() + }, ) } } @@ -413,7 +411,7 @@ impl<'s> Tokens<'s> { "auto" => NodeKind::Auto, "true" => NodeKind::Bool(true), "false" => NodeKind::Bool(false), - id => keyword(id).unwrap_or(NodeKind::Ident(id.into())), + id => keyword(id).unwrap_or_else(|| NodeKind::Ident(id.into())), } } @@ -461,9 +459,7 @@ impl<'s> Tokens<'s> { "in" => NodeKind::Length(f, LengthUnit::In), "deg" => NodeKind::Angle(f, AngularUnit::Deg), "rad" => NodeKind::Angle(f, AngularUnit::Rad), - _ => { - return NodeKind::Unknown(all.into()); - } + _ => NodeKind::Unknown(all.into()), } } else { NodeKind::Unknown(all.into()) diff --git a/src/style/mod.rs b/src/style/mod.rs index dccc7543..60734716 100644 --- a/src/style/mod.rs +++ b/src/style/mod.rs @@ -4,12 +4,14 @@ mod paper; pub use paper::*; +use std::fmt::{self, Debug, Formatter}; use std::rc::Rc; use ttf_parser::Tag; use crate::font::*; use crate::geom::*; +use crate::util::EcoString; /// Defines a set of properties a template can be instantiated with. #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -192,7 +194,7 @@ impl TextStyle { &[] }; - head.iter().chain(core).chain(tail).map(String::as_str) + head.iter().chain(core).chain(tail).map(EcoString::as_str) } /// Access the `families` style mutably. @@ -232,33 +234,57 @@ impl Default for TextStyle { #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FamilyStyle { /// The user-defined list of font families. - pub list: Rc<Vec<FontFamily>>, + pub list: Vec<FontFamily>, /// Definition of serif font families. - pub serif: Rc<Vec<String>>, + pub serif: Vec<EcoString>, /// Definition of sans-serif font families. - pub sans_serif: Rc<Vec<String>>, + pub sans_serif: Vec<EcoString>, /// Definition of monospace font families used for raw text. - pub monospace: Rc<Vec<String>>, + pub monospace: Vec<EcoString>, /// Base fonts that are tried as last resort. - pub base: Rc<Vec<String>>, + pub base: Vec<EcoString>, } impl Default for FamilyStyle { fn default() -> Self { Self { - list: Rc::new(vec![FontFamily::SansSerif]), - serif: Rc::new(vec!["ibm plex serif".into()]), - sans_serif: Rc::new(vec!["ibm plex sans".into()]), - monospace: Rc::new(vec!["ibm plex mono".into()]), - base: Rc::new(vec![ + list: vec![FontFamily::SansSerif], + serif: vec!["ibm plex serif".into()], + sans_serif: vec!["ibm plex sans".into()], + monospace: vec!["ibm plex mono".into()], + base: vec![ "ibm plex sans".into(), "latin modern math".into(), "twitter color emoji".into(), - ]), + ], } } } +/// A generic or named font family. +#[derive(Clone, Eq, PartialEq, Hash)] +pub enum FontFamily { + /// A family that has "serifs", small strokes attached to letters. + Serif, + /// A family in which glyphs do not have "serifs", small attached strokes. + SansSerif, + /// A family in which (almost) all glyphs are of equal width. + Monospace, + /// A specific family with a name. + Named(EcoString), +} + +impl Debug for FontFamily { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + f.pad(match self { + Self::Serif => "serif", + Self::SansSerif => "sans-serif", + Self::Monospace => "monospace", + Self::Named(s) => s, + }) + } +} + /// Whether various kinds of ligatures should appear. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FontFeatures { @@ -269,7 +295,7 @@ pub struct FontFeatures { /// Whether to apply stylistic alternates. ("salt") pub alternates: bool, /// Which stylistic set to apply. ("ss01" - "ss20") - pub stylistic_set: Option<u8>, + pub stylistic_set: StylisticSet, /// Configuration of ligature features. pub ligatures: LigatureFeatures, /// Configuration of numbers features. @@ -284,7 +310,7 @@ impl Default for FontFeatures { kerning: true, smallcaps: false, alternates: false, - stylistic_set: None, + stylistic_set: StylisticSet::default(), ligatures: LigatureFeatures::default(), numbers: NumberFeatures::default(), raw: vec![], @@ -292,6 +318,16 @@ impl Default for FontFeatures { } } +/// A stylistic set in a font face. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct StylisticSet(pub Option<u8>); + +impl Default for StylisticSet { + fn default() -> Self { + Self(None) + } +} + /// Whether various kinds of ligatures should appear. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LigatureFeatures { diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index 288c749a..0849dd58 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -65,9 +65,9 @@ impl Markup { NodeKind::Emph => Some(MarkupNode::Emph), NodeKind::Text(s) => Some(MarkupNode::Text(s.clone())), NodeKind::UnicodeEscape(c) => Some(MarkupNode::Text((*c).into())), - NodeKind::EnDash => Some(MarkupNode::Text("\u{2013}".into())), - NodeKind::EmDash => Some(MarkupNode::Text("\u{2014}".into())), - NodeKind::NonBreakingSpace => Some(MarkupNode::Text("\u{00A0}".into())), + NodeKind::EnDash => Some(MarkupNode::Text('\u{2013}'.into())), + NodeKind::EmDash => Some(MarkupNode::Text('\u{2014}'.into())), + NodeKind::NonBreakingSpace => Some(MarkupNode::Text('\u{00A0}'.into())), NodeKind::Math(math) => Some(MarkupNode::Math(math.as_ref().clone())), NodeKind::Raw(raw) => Some(MarkupNode::Raw(raw.as_ref().clone())), NodeKind::Heading => node.cast().map(MarkupNode::Heading), @@ -175,7 +175,7 @@ impl EnumNode { self.0 .children() .find_map(|node| match node.kind() { - NodeKind::EnumNumbering(num) => Some(num.clone()), + NodeKind::EnumNumbering(num) => Some(*num), _ => None, }) .expect("enum node is missing number") diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index ca6ed243..6f04cdc9 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -30,7 +30,7 @@ impl Green { fn data(&self) -> &GreenData { match self { Green::Node(n) => &n.data, - Green::Token(t) => &t, + Green::Token(t) => t, } } @@ -55,7 +55,7 @@ impl Green { /// The node's children. pub fn children(&self) -> &[Green] { match self { - Green::Node(n) => &n.children(), + Green::Node(n) => n.children(), Green::Token(_) => &[], } } diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 47d96589..4d5b8819 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -88,6 +88,11 @@ impl Span { Self { end, ..self } } + /// Whether the span is a single point. + pub fn is_empty(self) -> bool { + self.start == self.end + } + /// The byte length of the spanned region. pub fn len(self) -> usize { self.end - self.start diff --git a/src/util/eco_string.rs b/src/util/eco_string.rs index ab8d5642..8cb83377 100644 --- a/src/util/eco_string.rs +++ b/src/util/eco_string.rs @@ -2,11 +2,21 @@ use std::borrow::Borrow; use std::cmp::Ordering; use std::fmt::{self, Debug, Display, Formatter, Write}; use std::hash::{Hash, Hasher}; -use std::ops::Deref; +use std::ops::{Add, AddAssign, Deref}; use std::rc::Rc; use super::RcExt; +/// Create a new [`EcoString`] from a format string. +macro_rules! format_eco { + ($($tts:tt)*) => {{ + use std::fmt::Write; + let mut s = $crate::util::EcoString::new(); + write!(s, $($tts)*).unwrap(); + s + }}; +} + /// An economical string with inline storage and clone-on-write semantics. #[derive(Clone)] pub struct EcoString(Repr); @@ -142,14 +152,38 @@ impl EcoString { } } + /// Convert the string to lowercase. + pub fn to_lowercase(&self) -> Self { + if let Repr::Small { mut buf, len } = self.0 { + if self.is_ascii() { + buf[.. usize::from(len)].make_ascii_lowercase(); + return Self(Repr::Small { buf, len }); + } + } + + self.as_str().to_lowercase().into() + } + + /// Convert the string to uppercase. + pub fn to_uppercase(&self) -> Self { + if let Repr::Small { mut buf, len } = self.0 { + if self.is_ascii() { + buf[.. usize::from(len)].make_ascii_uppercase(); + return Self(Repr::Small { buf, len }); + } + } + + self.as_str().to_uppercase().into() + } + /// Repeat this string `n` times. pub fn repeat(&self, n: usize) -> Self { if n == 0 { return Self::new(); } - if let Repr::Small { buf, len } = &self.0 { - let prev = usize::from(*len); + if let Repr::Small { buf, len } = self.0 { + let prev = usize::from(len); let new = prev.saturating_mul(n); if new <= LIMIT { let src = &buf[.. prev]; @@ -193,7 +227,18 @@ impl Default for EcoString { impl Debug for EcoString { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - Debug::fmt(self.as_str(), f) + f.write_char('"')?; + for c in self.chars() { + match c { + '\\' => f.write_str(r"\\")?, + '"' => f.write_str(r#"\""#)?, + '\n' => f.write_str(r"\n")?, + '\r' => f.write_str(r"\r")?, + '\t' => f.write_str(r"\t")?, + _ => f.write_char(c)?, + } + } + f.write_char('"') } } @@ -253,6 +298,21 @@ impl Write for EcoString { } } +impl Add for EcoString { + type Output = Self; + + fn add(mut self, rhs: Self) -> Self::Output { + self += rhs; + self + } +} + +impl AddAssign for EcoString { + fn add_assign(&mut self, rhs: Self) { + self.push_str(rhs.as_str()); + } +} + impl AsRef<str> for EcoString { fn as_ref(&self) -> &str { self @@ -383,6 +443,21 @@ mod tests { } #[test] + fn test_str_case() { + assert_eq!(EcoString::new().to_uppercase(), ""); + assert_eq!(EcoString::from("abc").to_uppercase(), "ABC"); + assert_eq!(EcoString::from("AΣ").to_lowercase(), "aς"); + assert_eq!( + EcoString::from("a").repeat(100).to_uppercase(), + EcoString::from("A").repeat(100) + ); + assert_eq!( + EcoString::from("Ö").repeat(20).to_lowercase(), + EcoString::from("ö").repeat(20) + ); + } + + #[test] fn test_str_repeat() { // Test with empty string. assert_eq!(EcoString::new().repeat(0), ""); diff --git a/src/util/mod.rs b/src/util/mod.rs index c4272c55..8b74aed6 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,5 +1,6 @@ //! Utilities. +#[macro_use] mod eco_string; mod mac_roman; |
