summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/eval/value.rs144
-rw-r--r--src/library/deco.rs2
-rw-r--r--src/library/mod.rs8
-rw-r--r--src/library/page.rs24
-rw-r--r--src/library/shape.rs6
-rw-r--r--src/library/text.rs27
-rw-r--r--src/style/mod.rs35
7 files changed, 161 insertions, 85 deletions
diff --git a/src/eval/value.rs b/src/eval/value.rs
index e224438a..dec5c6c0 100644
--- a/src/eval/value.rs
+++ b/src/eval/value.rs
@@ -252,43 +252,6 @@ pub trait Cast<V>: Sized {
fn cast(value: V) -> StrResult<Self>;
}
-impl Cast<Value> for Value {
- fn is(_: &Value) -> bool {
- true
- }
-
- fn cast(value: Value) -> StrResult<Self> {
- Ok(value)
- }
-}
-
-impl<T> Cast<Spanned<Value>> for T
-where
- T: Cast<Value>,
-{
- fn is(value: &Spanned<Value>) -> bool {
- T::is(&value.v)
- }
-
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- T::cast(value.v)
- }
-}
-
-impl<T> Cast<Spanned<Value>> for Spanned<T>
-where
- T: Cast<Value>,
-{
- fn is(value: &Spanned<Value>) -> bool {
- T::is(&value.v)
- }
-
- fn cast(value: Spanned<Value>) -> StrResult<Self> {
- let span = value.span;
- T::cast(value.v).map(|t| Spanned::new(t, span))
- }
-}
-
/// Implement traits for primitives.
macro_rules! primitive {
(
@@ -400,6 +363,113 @@ primitive! { Dict: "dictionary", Dict }
primitive! { Template: "template", Template }
primitive! { Function: "function", Func }
+impl Cast<Value> for Value {
+ fn is(_: &Value) -> bool {
+ true
+ }
+
+ fn cast(value: Value) -> StrResult<Self> {
+ Ok(value)
+ }
+}
+
+impl<T> Cast<Spanned<Value>> for T
+where
+ T: Cast<Value>,
+{
+ fn is(value: &Spanned<Value>) -> bool {
+ T::is(&value.v)
+ }
+
+ fn cast(value: Spanned<Value>) -> StrResult<Self> {
+ T::cast(value.v)
+ }
+}
+
+impl<T> Cast<Spanned<Value>> for Spanned<T>
+where
+ T: Cast<Value>,
+{
+ fn is(value: &Spanned<Value>) -> bool {
+ T::is(&value.v)
+ }
+
+ fn cast(value: Spanned<Value>) -> StrResult<Self> {
+ let span = value.span;
+ T::cast(value.v).map(|t| Spanned::new(t, span))
+ }
+}
+
+/// A value that can be automatically determined.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub enum Smart<T> {
+ /// The value should be determined smartly based on the
+ /// circumstances.
+ Auto,
+ /// A forced, specific value.
+ Custom(T),
+}
+
+impl<T> Smart<T> {
+ /// Returns the contained custom value or a provided default value.
+ pub fn unwrap_or(self, default: T) -> T {
+ match self {
+ Self::Auto => default,
+ Self::Custom(x) => x,
+ }
+ }
+}
+
+impl<T> Default for Smart<T> {
+ fn default() -> Self {
+ Self::Auto
+ }
+}
+
+impl<T> Cast<Value> for Option<T>
+where
+ T: Cast<Value>,
+{
+ fn is(value: &Value) -> bool {
+ matches!(value, Value::None) || T::is(value)
+ }
+
+ fn cast(value: Value) -> StrResult<Self> {
+ match value {
+ Value::None => Ok(None),
+ v => T::cast(v).map(Some).map_err(|msg| with_alternative(msg, "none")),
+ }
+ }
+}
+
+impl<T> Cast<Value> for Smart<T>
+where
+ T: Cast<Value>,
+{
+ fn is(value: &Value) -> bool {
+ matches!(value, Value::Auto) || T::is(value)
+ }
+
+ fn cast(value: Value) -> StrResult<Self> {
+ match value {
+ Value::Auto => Ok(Self::Auto),
+ v => T::cast(v)
+ .map(Self::Custom)
+ .map_err(|msg| with_alternative(msg, "auto")),
+ }
+ }
+}
+
+/// Transform `expected X, found Y` into `expected X or A, found Y`.
+fn with_alternative(msg: String, alt: &str) -> String {
+ let mut parts = msg.split(", found ");
+ if let (Some(a), Some(b)) = (parts.next(), parts.next()) {
+ format!("{} or {}, found {}", a, alt, b)
+ } else {
+ msg
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/src/library/deco.rs b/src/library/deco.rs
index 1f8c051f..cb065689 100644
--- a/src/library/deco.rs
+++ b/src/library/deco.rs
@@ -17,7 +17,7 @@ pub fn overline(_: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
}
fn line_impl(args: &mut Args, kind: LineKind) -> TypResult<Value> {
- let stroke = args.named("stroke")?.or_else(|| args.find()).map(Paint::Solid);
+ let stroke = args.named("stroke")?.or_else(|| args.find());
let thickness = args.named::<Linear>("thickness")?.or_else(|| args.find());
let offset = args.named("offset")?;
let extent = args.named("extent")?.unwrap_or_default();
diff --git a/src/library/mod.rs b/src/library/mod.rs
index 7b8acf9e..6260e6fc 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -26,7 +26,7 @@ mod prelude {
pub use std::rc::Rc;
pub use crate::diag::{At, TypResult};
- pub use crate::eval::{Args, EvalContext, Template, Value};
+ pub use crate::eval::{Args, EvalContext, Smart, Template, Value};
pub use crate::frame::*;
pub use crate::geom::*;
pub use crate::layout::*;
@@ -144,3 +144,9 @@ dynamic! {
FontFamily: "font family",
Value::Str(string) => Self::Named(string.to_lowercase()),
}
+
+castable! {
+ Paint,
+ Expected: "color",
+ Value::Color(color) => Paint::Solid(color),
+}
diff --git a/src/library/page.rs b/src/library/page.rs
index 20871bd9..b256a521 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -12,13 +12,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
let paper = args.named::<Paper>("paper")?.or_else(|| args.find());
let width = args.named("width")?;
let height = args.named("height")?;
+ let flip = args.named("flip")?;
let margins = args.named("margins")?;
let left = args.named("left")?;
let top = args.named("top")?;
let right = args.named("right")?;
let bottom = args.named("bottom")?;
- let flip = args.named("flip")?;
- let fill = args.named("fill")?.map(Paint::Solid);
+ let fill = args.named("fill")?;
ctx.template.modify(move |style| {
let page = style.page_mut();
@@ -33,37 +33,37 @@ pub fn page(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
page.size.w = width;
}
+ if flip.unwrap_or(false) {
+ std::mem::swap(&mut page.size.w, &mut page.size.h);
+ }
+
if let Some(height) = height {
page.class = PaperClass::Custom;
page.size.h = height;
}
if let Some(margins) = margins {
- page.margins = Sides::splat(Some(margins));
+ page.margins = Sides::splat(margins);
}
if let Some(left) = left {
- page.margins.left = Some(left);
+ page.margins.left = left;
}
if let Some(top) = top {
- page.margins.top = Some(top);
+ page.margins.top = top;
}
if let Some(right) = right {
- page.margins.right = Some(right);
+ page.margins.right = right;
}
if let Some(bottom) = bottom {
- page.margins.bottom = Some(bottom);
- }
-
- if flip.unwrap_or(false) {
- std::mem::swap(&mut page.size.w, &mut page.size.h);
+ page.margins.bottom = bottom;
}
if let Some(fill) = fill {
- page.fill = Some(fill);
+ page.fill = fill;
}
});
diff --git a/src/library/shape.rs b/src/library/shape.rs
index abf927e4..f47da82f 100644
--- a/src/library/shape.rs
+++ b/src/library/shape.rs
@@ -58,11 +58,11 @@ fn shape_impl(
};
// Parse fill & stroke.
- let fill = args.named("fill")?.map(Paint::Solid);
+ let fill = args.named("fill")?.unwrap_or(None);
let stroke = match (args.named("stroke")?, args.named("thickness")?) {
(None, None) => fill.is_none().then(|| default),
- (color, thickness) => Some(Stroke {
- paint: color.map(Paint::Solid).unwrap_or(default.paint),
+ (color, thickness) => color.unwrap_or(Some(default.paint)).map(|paint| Stroke {
+ paint,
thickness: thickness.unwrap_or(default.thickness),
}),
};
diff --git a/src/library/text.rs b/src/library/text.rs
index d0b5c8e6..c0ee80e1 100644
--- a/src/library/text.rs
+++ b/src/library/text.rs
@@ -93,18 +93,16 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
castable! {
StylisticSet,
- Expected: "none or integer",
- Value::None => Self(None),
+ Expected: "integer",
Value::Int(v) => match v {
- 1 ..= 20 => Self(Some(v as u8)),
+ 1 ..= 20 => Self::new(v as u8),
_ => Err("must be between 1 and 20")?,
},
}
castable! {
NumberType,
- Expected: "auto or string",
- Value::Auto => Self::Auto,
+ Expected: "string",
Value::Str(string) => match string.as_str() {
"lining" => Self::Lining,
"old-style" => Self::OldStyle,
@@ -114,8 +112,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> TypResult<Value> {
castable! {
NumberWidth,
- Expected: "auto or string",
- Value::Auto => Self::Auto,
+ Expected: "string",
Value::Str(string) => match string.as_str() {
"proportional" => Self::Proportional,
"tabular" => Self::Tabular,
@@ -629,8 +626,8 @@ fn tags(features: &FontFeatures) -> Vec<Feature> {
}
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];
+ if let Some(set) = features.stylistic_set {
+ storage = [b's', b's', b'0' + set.get() / 10, b'0' + set.get() % 10];
feat(&storage, 1);
}
@@ -648,15 +645,15 @@ fn tags(features: &FontFeatures) -> Vec<Feature> {
}
match features.numbers.type_ {
- NumberType::Auto => {}
- NumberType::Lining => feat(b"lnum", 1),
- NumberType::OldStyle => feat(b"onum", 1),
+ Smart::Auto => {}
+ Smart::Custom(NumberType::Lining) => feat(b"lnum", 1),
+ Smart::Custom(NumberType::OldStyle) => feat(b"onum", 1),
}
match features.numbers.width {
- NumberWidth::Auto => {}
- NumberWidth::Proportional => feat(b"pnum", 1),
- NumberWidth::Tabular => feat(b"tnum", 1),
+ Smart::Auto => {}
+ Smart::Custom(NumberWidth::Proportional) => feat(b"pnum", 1),
+ Smart::Custom(NumberWidth::Tabular) => feat(b"tnum", 1),
}
match features.numbers.position {
diff --git a/src/style/mod.rs b/src/style/mod.rs
index 4a8830f8..45dbeb54 100644
--- a/src/style/mod.rs
+++ b/src/style/mod.rs
@@ -9,6 +9,7 @@ use std::rc::Rc;
use ttf_parser::Tag;
+use crate::eval::Smart;
use crate::font::*;
use crate::geom::*;
use crate::util::EcoString;
@@ -70,7 +71,7 @@ pub struct PageStyle {
pub size: Size,
/// The amount of white space on each side of the page. If a side is set to
/// `None`, the default for the paper class is used.
- pub margins: Sides<Option<Linear>>,
+ pub margins: Sides<Smart<Linear>>,
/// The background fill of the page.
pub fill: Option<Paint>,
}
@@ -94,7 +95,7 @@ impl Default for PageStyle {
Self {
class: paper.class(),
size: paper.size(),
- margins: Sides::splat(None),
+ margins: Sides::splat(Smart::Auto),
fill: None,
}
}
@@ -301,7 +302,7 @@ pub struct FontFeatures {
/// Whether to apply stylistic alternates. ("salt")
pub alternates: bool,
/// Which stylistic set to apply. ("ss01" - "ss20")
- pub stylistic_set: StylisticSet,
+ pub stylistic_set: Option<StylisticSet>,
/// Configuration of ligature features.
pub ligatures: LigatureFeatures,
/// Configuration of numbers features.
@@ -316,7 +317,7 @@ impl Default for FontFeatures {
kerning: true,
smallcaps: false,
alternates: false,
- stylistic_set: StylisticSet::default(),
+ stylistic_set: None,
ligatures: LigatureFeatures::default(),
numbers: NumberFeatures::default(),
raw: vec![],
@@ -326,11 +327,17 @@ impl Default for FontFeatures {
/// A stylistic set in a font face.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct StylisticSet(pub Option<u8>);
+pub struct StylisticSet(u8);
-impl Default for StylisticSet {
- fn default() -> Self {
- Self(None)
+impl StylisticSet {
+ /// Creates a new set, clamping to 1-20.
+ pub fn new(index: u8) -> Self {
+ Self(index.clamp(1, 20))
+ }
+
+ /// Get the value, guaranteed to be 1-20.
+ pub fn get(self) -> u8 {
+ self.0
}
}
@@ -359,9 +366,9 @@ impl Default for LigatureFeatures {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct NumberFeatures {
/// Whether to use lining or old-style numbers.
- pub type_: NumberType,
+ pub type_: Smart<NumberType>,
/// Whether to use proportional or tabular numbers.
- pub width: NumberWidth,
+ pub width: Smart<NumberWidth>,
/// How to position numbers vertically.
pub position: NumberPosition,
/// Whether to have a slash through the zero glyph. ("zero")
@@ -373,8 +380,8 @@ pub struct NumberFeatures {
impl Default for NumberFeatures {
fn default() -> Self {
Self {
- type_: NumberType::Auto,
- width: NumberWidth::Auto,
+ type_: Smart::Auto,
+ width: Smart::Auto,
position: NumberPosition::Normal,
slashed_zero: false,
fractions: false,
@@ -385,8 +392,6 @@ impl Default for NumberFeatures {
/// Which kind of numbers / figures to select.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum NumberType {
- /// Select the font's preference.
- Auto,
/// Numbers that fit well with capital text. ("lnum")
Lining,
/// Numbers that fit well into flow of upper- and lowercase text. ("onum")
@@ -396,8 +401,6 @@ pub enum NumberType {
/// The width of numbers / figures.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum NumberWidth {
- /// Select the font's preference.
- Auto,
/// Number widths are glyph specific. ("pnum")
Proportional,
/// All numbers are of equal width / monospaced. ("tnum")