summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-11-22 14:30:43 +0100
committerLaurenz <laurmaedje@gmail.com>2021-11-29 16:47:35 +0100
commited50661378f356e02c6ec943bc4840091d33cfbd (patch)
tree7ed51339ea1a4b7ccc4308c902b36e86f9c07e26
parentcef46e6c40fed0089a20e44ff2f251c06878891c (diff)
Castable optional and smart values
-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
-rw-r--r--tests/ref/elements/fill-stroke.pngbin0 -> 1942 bytes
-rw-r--r--tests/ref/layout/page.pngbin5829 -> 4157 bytes
-rw-r--r--tests/ref/layout/pagebreak.pngbin1254 -> 3145 bytes
-rw-r--r--tests/typ/elements/fill-stroke.typ26
-rw-r--r--tests/typ/layout/page.typ20
-rw-r--r--tests/typ/layout/pagebreak.typ14
-rw-r--r--tests/typ/text/features.typ8
-rw-r--r--tests/typeset.rs4
15 files changed, 215 insertions, 103 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")
diff --git a/tests/ref/elements/fill-stroke.png b/tests/ref/elements/fill-stroke.png
new file mode 100644
index 00000000..2d04b3dd
--- /dev/null
+++ b/tests/ref/elements/fill-stroke.png
Binary files differ
diff --git a/tests/ref/layout/page.png b/tests/ref/layout/page.png
index dae3d488..57e4b8f1 100644
--- a/tests/ref/layout/page.png
+++ b/tests/ref/layout/page.png
Binary files differ
diff --git a/tests/ref/layout/pagebreak.png b/tests/ref/layout/pagebreak.png
index 043b5086..fbb1d00c 100644
--- a/tests/ref/layout/pagebreak.png
+++ b/tests/ref/layout/pagebreak.png
Binary files differ
diff --git a/tests/typ/elements/fill-stroke.typ b/tests/typ/elements/fill-stroke.typ
new file mode 100644
index 00000000..b8f2b4bc
--- /dev/null
+++ b/tests/typ/elements/fill-stroke.typ
@@ -0,0 +1,26 @@
+// Test shape fill & stroke.
+
+---
+#let rect with (width: 20pt, height: 10pt)
+#let items = for i, rect in (
+ rect(stroke: none),
+ rect(),
+ rect(fill: none),
+ rect(thickness: 2pt),
+ rect(stroke: eastern),
+ rect(stroke: eastern, thickness: 2pt),
+ rect(fill: eastern),
+ rect(fill: eastern, stroke: none),
+ rect(fill: forest, stroke: none, thickness: 2pt),
+ rect(fill: forest, stroke: conifer),
+ rect(fill: forest, thickness: 2pt),
+ rect(fill: forest, stroke: conifer, thickness: 2pt),
+) {
+ (align(vertical: center)[{i + 1}.], rect, [])
+}
+
+#grid(
+ columns: (auto, auto, 1fr, auto, auto, 0fr),
+ gutter: 5pt,
+ ..items,
+)
diff --git a/tests/typ/layout/page.typ b/tests/typ/layout/page.typ
index 9bb3097d..1f707327 100644
--- a/tests/typ/layout/page.typ
+++ b/tests/typ/layout/page.typ
@@ -27,20 +27,8 @@
[#page(paper: "a11", flip: true) Flipped A11]
---
-// Test a combination of pages with bodies and normal content.
-
-#page(width: 80pt, height: 30pt)
-
-[#page() First]
-[#page() Second]
-#pagebreak()
-#pagebreak()
-Fourth
-[#page(height: 25pt)]
-Sixth
-[#page() Seventh]
-
----
#page(width: 80pt, height: 40pt, fill: eastern)
-#font(15pt, "Roboto", fill: white, smallcaps: true)
-Typst
+#font(15pt, "Roboto", fill: white, smallcaps: true)[Typst]
+
+#page(width: 40pt, fill: none, margins: auto, top: 10pt)
+Hi
diff --git a/tests/typ/layout/pagebreak.typ b/tests/typ/layout/pagebreak.typ
index ab591c87..f9a935bc 100644
--- a/tests/typ/layout/pagebreak.typ
+++ b/tests/typ/layout/pagebreak.typ
@@ -18,3 +18,17 @@ C
// No consequences from the page("A4") call here.
#pagebreak()
D
+
+---
+// Test a combination of pages with bodies and normal content.
+
+#page(width: 80pt, height: 30pt)
+
+[#page() First]
+[#page() Second]
+#pagebreak()
+#pagebreak()
+Fourth
+[#page(height: 25pt)]
+Sixth
+[#page() Seventh]
diff --git a/tests/typ/text/features.typ b/tests/typ/text/features.typ
index fc84514b..d60583d8 100644
--- a/tests/typ/text/features.typ
+++ b/tests/typ/text/features.typ
@@ -53,10 +53,18 @@ fi vs. #font(ligatures: false)[No fi] \
fi vs. #font(features: (liga: 0))[No fi]
---
+// Error: 22-27 expected integer or none, found boolean
+#font(stylistic-set: false)
+
+---
// Error: 22-24 must be between 1 and 20
#font(stylistic-set: 25)
---
+// Error: 20-21 expected string or auto, found integer
+#font(number-type: 2)
+
+---
// Error: 20-31 expected "lining" or "old-style"
#font(number-type: "different")
diff --git a/tests/typeset.rs b/tests/typeset.rs
index 723df4a8..6a21cf7b 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -10,7 +10,7 @@ use ttf_parser::{GlyphId, OutlineBuilder};
use walkdir::WalkDir;
use typst::diag::Error;
-use typst::eval::Value;
+use typst::eval::{Smart, Value};
use typst::font::Face;
use typst::frame::{Element, Frame, Geometry, Shape, Stroke, Text};
use typst::geom::{self, Color, Length, Paint, PathElement, RgbaColor, Sides, Size};
@@ -64,7 +64,7 @@ fn main() {
// large and fit them to match their content.
let mut style = Style::default();
style.page_mut().size = Size::new(Length::pt(120.0), Length::inf());
- style.page_mut().margins = Sides::splat(Some(Length::pt(10.0).into()));
+ style.page_mut().margins = Sides::splat(Smart::Custom(Length::pt(10.0).into()));
style.text_mut().size = Length::pt(10.0);
// Hook up an assert function into the global scope.