mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Int, Float, Relative and Linear values 🍉
This commit is contained in:
parent
0fc25d732d
commit
95bae5725c
@ -6,8 +6,8 @@ use std::ops::Index;
|
||||
|
||||
use crate::syntax::{Span, Spanned};
|
||||
|
||||
/// A dictionary data structure, which maps from integers (`u64`) or strings to
|
||||
/// a generic value type.
|
||||
/// A dictionary data structure, which maps from integers and strings to a
|
||||
/// generic value type.
|
||||
///
|
||||
/// The dictionary can be used to model arrays by assigning values to successive
|
||||
/// indices from `0..n`. The `push` method offers special support for this
|
||||
@ -293,32 +293,40 @@ impl<'a> From<&'a str> for RefKey<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A dictionary entry which tracks key and value span.
|
||||
/// A dictionary entry which combines key span and value.
|
||||
///
|
||||
/// This exists because a key in a directory can't track its span by itself.
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct SpannedEntry<V> {
|
||||
pub key: Span,
|
||||
pub val: Spanned<V>,
|
||||
pub key_span: Span,
|
||||
pub value: Spanned<V>,
|
||||
}
|
||||
|
||||
impl<V> SpannedEntry<V> {
|
||||
/// Create a new entry.
|
||||
pub fn new(key: Span, val: Spanned<V>) -> Self {
|
||||
Self { key, val }
|
||||
Self { key_span: key, value: val }
|
||||
}
|
||||
|
||||
/// Create an entry with the same span for key and value.
|
||||
pub fn val(val: Spanned<V>) -> Self {
|
||||
Self { key: val.span, val }
|
||||
Self { key_span: val.span, value: val }
|
||||
}
|
||||
|
||||
/// Convert from `&SpannedEntry<T>` to `SpannedEntry<&T>`
|
||||
pub fn as_ref(&self) -> SpannedEntry<&V> {
|
||||
SpannedEntry { key: self.key, val: self.val.as_ref() }
|
||||
SpannedEntry {
|
||||
key_span: self.key_span,
|
||||
value: self.value.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Map the entry to a different value type.
|
||||
pub fn map<U>(self, f: impl FnOnce(V) -> U) -> SpannedEntry<U> {
|
||||
SpannedEntry { key: self.key, val: self.val.map(f) }
|
||||
SpannedEntry {
|
||||
key_span: self.key_span,
|
||||
value: self.value.map(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -326,10 +334,10 @@ impl<V: Debug> Debug for SpannedEntry<V> {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
f.write_str("key")?;
|
||||
self.key.fmt(f)?;
|
||||
self.key_span.fmt(f)?;
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
self.val.fmt(f)
|
||||
self.value.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,8 @@ use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use super::dict::{Dict, SpannedEntry};
|
||||
use crate::color::RgbaColor;
|
||||
use crate::geom::Linear;
|
||||
use crate::layout::{Command, Commands, Dir, LayoutContext, SpecAlign};
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::paper::Paper;
|
||||
use crate::syntax::{Ident, Span, SpanWith, Spanned, SynNode, SynTree};
|
||||
use crate::{DynFuture, Feedback, Pass};
|
||||
@ -21,10 +21,21 @@ pub enum Value {
|
||||
Ident(Ident),
|
||||
/// A boolean: `true, false`.
|
||||
Bool(bool),
|
||||
/// A number: `1.2, 200%`.
|
||||
Number(f64),
|
||||
/// An integer: `120`.
|
||||
Int(i64),
|
||||
/// A floating-point number: `1.2, 200%`.
|
||||
Float(f64),
|
||||
/// A length: `2cm, 5.2in`.
|
||||
Length(Length),
|
||||
Length(f64),
|
||||
/// A relative value: `50%`.
|
||||
///
|
||||
/// Note: `50%` is represented as `0.5` here, but as `50.0` in the
|
||||
/// corresponding [literal].
|
||||
///
|
||||
/// [literal]: ../syntax/ast/enum.Lit.html#variant.Percent
|
||||
Relative(f64),
|
||||
/// A combination of an absolute length and a relative value.
|
||||
Linear(Linear),
|
||||
/// A color value with alpha channel: `#f79143ff`.
|
||||
Color(RgbaColor),
|
||||
/// A string: `"string"`.
|
||||
@ -40,14 +51,16 @@ pub enum Value {
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// A natural-language name of the type of this expression, e.g.
|
||||
/// "identifier".
|
||||
/// The natural-language name of this value for use in error messages.
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Ident(_) => "identifier",
|
||||
Self::Bool(_) => "bool",
|
||||
Self::Number(_) => "number",
|
||||
Self::Int(_) => "integer",
|
||||
Self::Float(_) => "float",
|
||||
Self::Relative(_) => "relative",
|
||||
Self::Length(_) => "length",
|
||||
Self::Linear(_) => "linear",
|
||||
Self::Color(_) => "color",
|
||||
Self::Str(_) => "string",
|
||||
Self::Dict(_) => "dict",
|
||||
@ -71,13 +84,13 @@ impl Spanned<Value> {
|
||||
let mut end = None;
|
||||
for entry in dict.into_values() {
|
||||
if let Some(last_end) = end {
|
||||
let span = Span::new(last_end, entry.key.start);
|
||||
let span = Span::new(last_end, entry.key_span.start);
|
||||
let tree = vec![SynNode::Space.span_with(span)];
|
||||
commands.push(Command::LayoutSyntaxTree(tree));
|
||||
}
|
||||
|
||||
end = Some(entry.val.span.end);
|
||||
commands.extend(entry.val.into_commands());
|
||||
end = Some(entry.value.span.end);
|
||||
commands.extend(entry.value.into_commands());
|
||||
}
|
||||
commands
|
||||
}
|
||||
@ -100,11 +113,14 @@ impl Debug for Value {
|
||||
match self {
|
||||
Self::Ident(i) => i.fmt(f),
|
||||
Self::Bool(b) => b.fmt(f),
|
||||
Self::Number(n) => n.fmt(f),
|
||||
Self::Length(s) => s.fmt(f),
|
||||
Self::Int(i) => i.fmt(f),
|
||||
Self::Float(n) => n.fmt(f),
|
||||
Self::Length(l) => l.fmt(f),
|
||||
Self::Relative(r) => r.fmt(f),
|
||||
Self::Linear(l) => l.fmt(f),
|
||||
Self::Color(c) => c.fmt(f),
|
||||
Self::Str(s) => s.fmt(f),
|
||||
Self::Dict(t) => t.fmt(f),
|
||||
Self::Dict(d) => d.fmt(f),
|
||||
Self::Tree(t) => t.fmt(f),
|
||||
Self::Func(c) => c.fmt(f),
|
||||
Self::Commands(c) => c.fmt(f),
|
||||
@ -117,18 +133,19 @@ impl Debug for Value {
|
||||
/// The first argument is a dictionary containing the arguments passed to the
|
||||
/// function. The function may be asynchronous (as such it returns a dynamic
|
||||
/// future) and it may emit diagnostics, which are contained in the returned
|
||||
/// `Pass`. In the end, the function must evaluate to `Value`. Your typical
|
||||
/// `Pass`. In the end, the function must evaluate to [`Value`]. A typical
|
||||
/// typesetting function will return a `Commands` value which will instruct the
|
||||
/// layouting engine to do what the function pleases.
|
||||
///
|
||||
/// The dynamic function object is wrapped in an `Rc` to keep `Value` clonable.
|
||||
/// The dynamic function object is wrapped in an `Rc` to keep [`Value`]
|
||||
/// clonable.
|
||||
///
|
||||
/// [`Value`]: enum.Value.html
|
||||
#[derive(Clone)]
|
||||
pub struct FuncValue(pub Rc<FuncType>);
|
||||
|
||||
/// The dynamic function type backtick [`FuncValue`].
|
||||
///
|
||||
/// [`FuncValue`]: struct.FuncValue.html
|
||||
pub type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>;
|
||||
/// The signature of executable functions.
|
||||
type FuncType = dyn Fn(Span, DictValue, LayoutContext<'_>) -> DynFuture<Pass<Value>>;
|
||||
|
||||
impl FuncValue {
|
||||
/// Create a new function value from a rust function or closure.
|
||||
@ -175,7 +192,7 @@ impl DictValue {
|
||||
/// skipping and ignoring all non-matching entries with lower keys.
|
||||
pub fn take<T: TryFromValue>(&mut self) -> Option<T> {
|
||||
for (&key, entry) in self.nums() {
|
||||
let expr = entry.val.as_ref();
|
||||
let expr = entry.value.as_ref();
|
||||
if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
|
||||
self.remove(key);
|
||||
return Some(val);
|
||||
@ -197,7 +214,7 @@ impl DictValue {
|
||||
) -> Option<T> {
|
||||
while let Some((num, _)) = self.first() {
|
||||
let entry = self.remove(num).unwrap();
|
||||
if let Some(val) = T::try_from_value(entry.val.as_ref(), f) {
|
||||
if let Some(val) = T::try_from_value(entry.value.as_ref(), f) {
|
||||
return Some(val);
|
||||
}
|
||||
}
|
||||
@ -214,7 +231,7 @@ impl DictValue {
|
||||
T: TryFromValue,
|
||||
{
|
||||
self.remove(key).and_then(|entry| {
|
||||
let expr = entry.val.as_ref();
|
||||
let expr = entry.value.as_ref();
|
||||
T::try_from_value(expr, f)
|
||||
})
|
||||
}
|
||||
@ -230,7 +247,7 @@ impl DictValue {
|
||||
let mut skip = 0;
|
||||
std::iter::from_fn(move || {
|
||||
for (&key, entry) in self.nums().skip(skip) {
|
||||
let expr = entry.val.as_ref();
|
||||
let expr = entry.value.as_ref();
|
||||
if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
|
||||
self.remove(key);
|
||||
return Some((key, val));
|
||||
@ -265,7 +282,7 @@ impl DictValue {
|
||||
let mut skip = 0;
|
||||
std::iter::from_fn(move || {
|
||||
for (key, entry) in self.strs().skip(skip) {
|
||||
let expr = entry.val.as_ref();
|
||||
let expr = entry.value.as_ref();
|
||||
if let Some(val) = T::try_from_value(expr, &mut Feedback::new()) {
|
||||
let key = key.clone();
|
||||
self.remove(&key);
|
||||
@ -281,7 +298,7 @@ impl DictValue {
|
||||
/// Generated `"unexpected argument"` errors for all remaining entries.
|
||||
pub fn unexpected(&self, f: &mut Feedback) {
|
||||
for entry in self.values() {
|
||||
error!(@f, entry.key.join(entry.val.span), "unexpected argument");
|
||||
error!(@f, entry.key_span.join(entry.value.span), "unexpected argument");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,20 +368,50 @@ impl<T: TryFromValue> TryFromValue for Spanned<T> {
|
||||
|
||||
impl_match!(Value, "value", v => v.clone());
|
||||
impl_match!(Ident, "identifier", Value::Ident(i) => i.clone());
|
||||
impl_match!(String, "string", Value::Str(s) => s.clone());
|
||||
impl_match!(bool, "bool", &Value::Bool(b) => b);
|
||||
impl_match!(f64, "number", &Value::Number(n) => n);
|
||||
impl_match!(Length, "length", &Value::Length(l) => l);
|
||||
impl_match!(i64, "integer", &Value::Int(i) => i);
|
||||
impl_match!(f64, "float",
|
||||
&Value::Int(i) => i as f64,
|
||||
&Value::Float(f) => f,
|
||||
);
|
||||
impl_match!(Abs, "length", &Value::Length(l) => Abs(l));
|
||||
impl_match!(Rel, "relative", &Value::Relative(r) => Rel(r));
|
||||
impl_match!(Linear, "linear",
|
||||
&Value::Linear(l) => l,
|
||||
&Value::Length(l) => Linear::abs(l),
|
||||
&Value::Relative(r) => Linear::rel(r),
|
||||
);
|
||||
impl_match!(String, "string", Value::Str(s) => s.clone());
|
||||
impl_match!(SynTree, "tree", Value::Tree(t) => t.clone());
|
||||
impl_match!(DictValue, "dict", Value::Dict(t) => t.clone());
|
||||
impl_match!(FuncValue, "function", Value::Func(f) => f.clone());
|
||||
impl_match!(ScaleLength, "number or length",
|
||||
&Value::Length(length) => ScaleLength::Absolute(length),
|
||||
&Value::Number(scale) => ScaleLength::Scaled(scale),
|
||||
);
|
||||
|
||||
/// A value type that matches identifiers and strings and implements
|
||||
/// `Into<String>`.
|
||||
/// A value type that matches [length] values.
|
||||
///
|
||||
/// [length]: enum.Value.html#variant.Length
|
||||
pub struct Abs(pub f64);
|
||||
|
||||
impl From<Abs> for f64 {
|
||||
fn from(abs: Abs) -> f64 {
|
||||
abs.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [relative] values.
|
||||
///
|
||||
/// [relative]: enum.Value.html#variant.Relative
|
||||
pub struct Rel(pub f64);
|
||||
|
||||
impl From<Rel> for f64 {
|
||||
fn from(rel: Rel) -> f64 {
|
||||
rel.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A value type that matches [identifier] and [string] values.
|
||||
///
|
||||
/// [identifier]: enum.Value.html#variant.Ident
|
||||
/// [string]: enum.Value.html#variant.Str
|
||||
pub struct StringLike(pub String);
|
||||
|
||||
impl From<StringLike> for String {
|
||||
@ -410,19 +457,19 @@ impl_ident!(Paper, "paper", Self::from_name);
|
||||
impl TryFromValue for FontWeight {
|
||||
fn try_from_value(value: Spanned<&Value>, f: &mut Feedback) -> Option<Self> {
|
||||
match value.v {
|
||||
&Value::Number(weight) => {
|
||||
const MIN: u16 = 100;
|
||||
const MAX: u16 = 900;
|
||||
|
||||
if weight < MIN as f64 {
|
||||
&Value::Int(weight) => {
|
||||
const MIN: i64 = 100;
|
||||
const MAX: i64 = 900;
|
||||
let weight = if weight < MIN {
|
||||
error!(@f, value.span, "the minimum font weight is {}", MIN);
|
||||
Some(Self::THIN)
|
||||
} else if weight > MAX as f64 {
|
||||
MIN
|
||||
} else if weight > MAX {
|
||||
error!(@f, value.span, "the maximum font weight is {}", MAX);
|
||||
Some(Self::BLACK)
|
||||
MAX
|
||||
} else {
|
||||
FontWeight::from_number(weight.round() as u16)
|
||||
}
|
||||
weight
|
||||
};
|
||||
Self::from_number(weight as u16)
|
||||
}
|
||||
Value::Ident(ident) => {
|
||||
let weight = Self::from_str(ident);
|
||||
@ -434,7 +481,7 @@ impl TryFromValue for FontWeight {
|
||||
other => {
|
||||
error!(
|
||||
@f, value.span,
|
||||
"expected font weight (name or number), found {}",
|
||||
"expected font weight (name or integer), found {}",
|
||||
other.name(),
|
||||
);
|
||||
None
|
||||
@ -490,7 +537,7 @@ mod tests {
|
||||
assert_eq!(dict.take_key::<f64>("hi", &mut f), None);
|
||||
assert_eq!(f.diagnostics, [error!(
|
||||
Span::ZERO,
|
||||
"expected number, found bool"
|
||||
"expected float, found bool"
|
||||
)]);
|
||||
assert!(dict.is_empty());
|
||||
}
|
||||
@ -499,13 +546,13 @@ mod tests {
|
||||
fn test_dict_take_all_removes_the_correct_entries() {
|
||||
let mut dict = Dict::new();
|
||||
dict.insert(1, entry(Value::Bool(false)));
|
||||
dict.insert(3, entry(Value::Number(0.0)));
|
||||
dict.insert(3, entry(Value::Float(0.0)));
|
||||
dict.insert(7, entry(Value::Bool(true)));
|
||||
assert_eq!(dict.take_all_num::<bool>().collect::<Vec<_>>(), [
|
||||
(1, false),
|
||||
(7, true)
|
||||
],);
|
||||
assert_eq!(dict.len(), 1);
|
||||
assert_eq!(dict[3].val.v, Value::Number(0.0));
|
||||
assert_eq!(dict[3].value.v, Value::Float(0.0));
|
||||
}
|
||||
}
|
||||
|
128
src/geom.rs
128
src/geom.rs
@ -3,6 +3,9 @@
|
||||
#[doc(no_inline)]
|
||||
pub use kurbo::*;
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::ops::*;
|
||||
|
||||
use crate::layout::primitive::{Dir, GenAlign, LayoutAlign, LayoutSystem, SpecAxis};
|
||||
|
||||
/// Additional methods for [sizes].
|
||||
@ -176,3 +179,128 @@ impl<T> Sides<T> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A function that depends linearly on one value.
|
||||
///
|
||||
/// This represents a function `f(x) = rel * x + abs`.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub struct Linear {
|
||||
/// The relative part.
|
||||
pub rel: f64,
|
||||
/// The absolute part.
|
||||
pub abs: f64,
|
||||
}
|
||||
|
||||
impl Linear {
|
||||
/// The constant zero function.
|
||||
pub const ZERO: Linear = Linear { rel: 0.0, abs: 0.0 };
|
||||
|
||||
/// Create a new linear function.
|
||||
pub fn new(rel: f64, abs: f64) -> Self {
|
||||
Self { rel, abs }
|
||||
}
|
||||
|
||||
/// Create a new linear function with only a relative component.
|
||||
pub fn rel(rel: f64) -> Self {
|
||||
Self { rel, abs: 0.0 }
|
||||
}
|
||||
|
||||
/// Create a new linear function with only an absolute component.
|
||||
pub fn abs(abs: f64) -> Self {
|
||||
Self { rel: 0.0, abs }
|
||||
}
|
||||
|
||||
/// Evaluate the linear function with the given value.
|
||||
pub fn eval(self, x: f64) -> f64 {
|
||||
self.rel * x + self.abs
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel + other.rel,
|
||||
abs: self.abs + other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Linear {
|
||||
fn add_assign(&mut self, other: Self) {
|
||||
self.rel += other.rel;
|
||||
self.abs += other.abs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self {
|
||||
rel: self.rel - other.rel,
|
||||
abs: self.abs - other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Linear {
|
||||
fn sub_assign(&mut self, other: Self) {
|
||||
self.rel -= other.rel;
|
||||
self.abs -= other.abs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, other: f64) -> Self {
|
||||
Self {
|
||||
rel: self.rel + other,
|
||||
abs: self.abs + other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f64> for Linear {
|
||||
fn mul_assign(&mut self, other: f64) {
|
||||
self.rel *= other;
|
||||
self.abs *= other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Linear> for f64 {
|
||||
type Output = Linear;
|
||||
|
||||
fn mul(self, other: Linear) -> Linear {
|
||||
Linear {
|
||||
rel: self + other.rel,
|
||||
abs: self + other.abs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Linear {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, other: f64) -> Self {
|
||||
Self {
|
||||
rel: self.rel / other,
|
||||
abs: self.abs / other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<f64> for Linear {
|
||||
fn div_assign(&mut self, other: f64) {
|
||||
self.rel /= other;
|
||||
self.abs /= other;
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Linear {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "{}x + {}", self.rel, self.abs)
|
||||
}
|
||||
}
|
||||
|
@ -115,7 +115,9 @@ impl<'a> TreeLayouter<'a> {
|
||||
|
||||
async fn layout_heading(&mut self, heading: &NodeHeading) {
|
||||
let style = self.style.text.clone();
|
||||
self.style.text.font_scale *= 1.5 - 0.1 * heading.level.v as f64;
|
||||
|
||||
let factor = 1.5 - 0.1 * heading.level.v as f64;
|
||||
self.style.text.font_size.scale *= factor;
|
||||
self.style.text.strong = true;
|
||||
|
||||
self.layout_parbreak();
|
||||
|
@ -177,38 +177,6 @@ impl Display for ParseLengthError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Either an absolute length or a factor of some entity.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum ScaleLength {
|
||||
Absolute(Length),
|
||||
Scaled(f64),
|
||||
}
|
||||
|
||||
impl ScaleLength {
|
||||
/// Use the absolute value or scale the entity.
|
||||
pub fn raw_scaled(&self, entity: f64) -> f64 {
|
||||
match *self {
|
||||
ScaleLength::Absolute(l) => l.as_raw(),
|
||||
ScaleLength::Scaled(s) => s * entity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ScaleLength {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ScaleLength::Absolute(length) => write!(f, "{}", length),
|
||||
ScaleLength::Scaled(scale) => write!(f, "{}%", scale * 100.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ScaleLength {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::length::ScaleLength;
|
||||
use crate::geom::Linear;
|
||||
|
||||
/// `box`: Layouts its contents into a box.
|
||||
///
|
||||
@ -19,17 +19,17 @@ pub async fn boxed(
|
||||
ctx.spaces.truncate(1);
|
||||
ctx.repeat = false;
|
||||
|
||||
if let Some(width) = args.take_key::<ScaleLength>("width", &mut f) {
|
||||
let length = width.raw_scaled(ctx.base.width);
|
||||
ctx.base.width = length;
|
||||
ctx.spaces[0].size.width = length;
|
||||
if let Some(width) = args.take_key::<Linear>("width", &mut f) {
|
||||
let abs = width.eval(ctx.base.width);
|
||||
ctx.base.width = abs;
|
||||
ctx.spaces[0].size.width = abs;
|
||||
ctx.spaces[0].expansion.horizontal = true;
|
||||
}
|
||||
|
||||
if let Some(height) = args.take_key::<ScaleLength>("height", &mut f) {
|
||||
let length = height.raw_scaled(ctx.base.height);
|
||||
ctx.base.height = length;
|
||||
ctx.spaces[0].size.height = length;
|
||||
if let Some(height) = args.take_key::<Linear>("height", &mut f) {
|
||||
let abs = height.eval(ctx.base.height);
|
||||
ctx.base.height = abs;
|
||||
ctx.spaces[0].size.height = abs;
|
||||
ctx.spaces[0].expansion.vertical = true;
|
||||
}
|
||||
|
||||
|
@ -5,20 +5,18 @@ use crate::color::RgbaColor;
|
||||
pub async fn rgb(span: Span, mut args: DictValue, _: LayoutContext<'_>) -> Pass<Value> {
|
||||
let mut f = Feedback::new();
|
||||
|
||||
let r = args.expect::<Spanned<f64>>("red value", span, &mut f);
|
||||
let g = args.expect::<Spanned<f64>>("green value", span, &mut f);
|
||||
let b = args.expect::<Spanned<f64>>("blue value", span, &mut f);
|
||||
let a = args.take::<Spanned<f64>>();
|
||||
let r = args.expect::<Spanned<i64>>("red value", span, &mut f);
|
||||
let g = args.expect::<Spanned<i64>>("green value", span, &mut f);
|
||||
let b = args.expect::<Spanned<i64>>("blue value", span, &mut f);
|
||||
let a = args.take::<Spanned<i64>>();
|
||||
|
||||
let mut clamp = |component: Option<Spanned<f64>>, default| {
|
||||
component
|
||||
.map(|c| {
|
||||
if c.v < 0.0 || c.v > 255.0 {
|
||||
error!(@f, c.span, "should be between 0 and 255")
|
||||
}
|
||||
c.v.min(255.0).max(0.0).round() as u8
|
||||
})
|
||||
.unwrap_or(default)
|
||||
let mut clamp = |component: Option<Spanned<i64>>, default| {
|
||||
component.map_or(default, |c| {
|
||||
if c.v < 0 || c.v > 255 {
|
||||
error!(@f, c.span, "should be between 0 and 255")
|
||||
}
|
||||
c.v.max(0).min(255) as u8
|
||||
})
|
||||
};
|
||||
|
||||
let color = RgbaColor::new(clamp(r, 0), clamp(g, 0), clamp(b, 0), clamp(a, 255));
|
||||
|
@ -2,7 +2,7 @@ use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||
|
||||
use super::*;
|
||||
use crate::eval::StringLike;
|
||||
use crate::length::ScaleLength;
|
||||
use crate::geom::Linear;
|
||||
|
||||
/// `font`: Configure the font.
|
||||
///
|
||||
@ -56,13 +56,12 @@ pub async fn font(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass<
|
||||
|
||||
let content = args.take::<SynTree>();
|
||||
|
||||
if let Some(s) = args.take::<ScaleLength>() {
|
||||
match s {
|
||||
ScaleLength::Absolute(length) => {
|
||||
text.base_font_size = length.as_raw();
|
||||
text.font_scale = 1.0;
|
||||
}
|
||||
ScaleLength::Scaled(scale) => text.font_scale = scale,
|
||||
if let Some(linear) = args.take::<Linear>() {
|
||||
if linear.rel == 0.0 {
|
||||
text.font_size.base = linear.abs;
|
||||
text.font_size.scale = Linear::rel(1.0);
|
||||
} else {
|
||||
text.font_size.scale = linear;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use std::mem;
|
||||
|
||||
use super::*;
|
||||
use crate::geom::Sides;
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::eval::Abs;
|
||||
use crate::geom::{Linear, Sides};
|
||||
use crate::paper::{Paper, PaperClass};
|
||||
|
||||
/// `page`: Configure pages.
|
||||
@ -28,33 +28,33 @@ pub async fn page(_: Span, mut args: DictValue, ctx: LayoutContext<'_>) -> Pass<
|
||||
style.size = paper.size();
|
||||
}
|
||||
|
||||
if let Some(width) = args.take_key::<Length>("width", &mut f) {
|
||||
if let Some(Abs(width)) = args.take_key::<Abs>("width", &mut f) {
|
||||
style.class = PaperClass::Custom;
|
||||
style.size.width = width.as_raw();
|
||||
style.size.width = width;
|
||||
}
|
||||
|
||||
if let Some(height) = args.take_key::<Length>("height", &mut f) {
|
||||
if let Some(Abs(height)) = args.take_key::<Abs>("height", &mut f) {
|
||||
style.class = PaperClass::Custom;
|
||||
style.size.height = height.as_raw();
|
||||
style.size.height = height;
|
||||
}
|
||||
|
||||
if let Some(margins) = args.take_key::<ScaleLength>("margins", &mut f) {
|
||||
if let Some(margins) = args.take_key::<Linear>("margins", &mut f) {
|
||||
style.margins = Sides::uniform(Some(margins));
|
||||
}
|
||||
|
||||
if let Some(left) = args.take_key::<ScaleLength>("left", &mut f) {
|
||||
if let Some(left) = args.take_key::<Linear>("left", &mut f) {
|
||||
style.margins.left = Some(left);
|
||||
}
|
||||
|
||||
if let Some(top) = args.take_key::<ScaleLength>("top", &mut f) {
|
||||
if let Some(top) = args.take_key::<Linear>("top", &mut f) {
|
||||
style.margins.top = Some(top);
|
||||
}
|
||||
|
||||
if let Some(right) = args.take_key::<ScaleLength>("right", &mut f) {
|
||||
if let Some(right) = args.take_key::<Linear>("right", &mut f) {
|
||||
style.margins.right = Some(right);
|
||||
}
|
||||
|
||||
if let Some(bottom) = args.take_key::<ScaleLength>("bottom", &mut f) {
|
||||
if let Some(bottom) = args.take_key::<Linear>("bottom", &mut f) {
|
||||
style.margins.bottom = Some(bottom);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::geom::Linear;
|
||||
use crate::layout::SpacingKind;
|
||||
use crate::length::ScaleLength;
|
||||
|
||||
/// `h`: Add horizontal spacing.
|
||||
///
|
||||
@ -26,10 +26,10 @@ fn spacing(
|
||||
) -> Pass<Value> {
|
||||
let mut f = Feedback::new();
|
||||
|
||||
let spacing = args.expect::<ScaleLength>("spacing", name, &mut f);
|
||||
let spacing = args.expect::<Linear>("spacing", name, &mut f);
|
||||
let commands = if let Some(spacing) = spacing {
|
||||
let axis = axis.to_gen(ctx.sys);
|
||||
let spacing = spacing.raw_scaled(ctx.style.text.font_size());
|
||||
let spacing = spacing.eval(ctx.style.text.font_size());
|
||||
vec![AddSpacing(spacing, SpacingKind::Hard, axis)]
|
||||
} else {
|
||||
vec![]
|
||||
|
20
src/paper.rs
20
src/paper.rs
@ -1,7 +1,7 @@
|
||||
//! Predefined papers.
|
||||
|
||||
use crate::geom::{Sides, Size};
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::geom::{Linear, Sides, Size};
|
||||
use crate::length::Length;
|
||||
|
||||
/// Specification of a paper.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
@ -38,15 +38,15 @@ pub enum PaperClass {
|
||||
|
||||
impl PaperClass {
|
||||
/// The default margin ratios for this page class.
|
||||
pub fn default_margins(self) -> Sides<ScaleLength> {
|
||||
let s = ScaleLength::Scaled;
|
||||
let f = |l, r, t, b| Sides::new(s(l), s(r), s(t), s(b));
|
||||
pub fn default_margins(self) -> Sides<Linear> {
|
||||
let f = Linear::rel;
|
||||
let s = |l, r, t, b| Sides::new(f(l), f(r), f(t), f(b));
|
||||
match self {
|
||||
Self::Custom => f(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::Base => f(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::US => f(0.1760, 0.1092, 0.1760, 0.0910),
|
||||
Self::Newspaper => f(0.0455, 0.0587, 0.0455, 0.0294),
|
||||
Self::Book => f(0.1200, 0.0852, 0.1500, 0.0965),
|
||||
Self::Custom => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::Base => s(0.1190, 0.0842, 0.1190, 0.0842),
|
||||
Self::US => s(0.1760, 0.1092, 0.1760, 0.0910),
|
||||
Self::Newspaper => s(0.0455, 0.0587, 0.0455, 0.0294),
|
||||
Self::Book => s(0.1200, 0.0852, 0.1500, 0.0965),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,7 @@ fn node(p: &mut Parser, at_start: bool) -> Option<Spanned<SynNode>> {
|
||||
SynNode::Text(p.eaten_from(start).into())
|
||||
}
|
||||
}
|
||||
Token::NonBreakingSpace => SynNode::Text("\u{00A0}".into()),
|
||||
Token::Raw(token) => SynNode::Raw(raw(p, token)),
|
||||
Token::UnicodeEscape(token) => SynNode::Text(unicode_escape(p, token, start)),
|
||||
|
||||
@ -425,8 +426,10 @@ fn value(p: &mut Parser) -> Option<Expr> {
|
||||
|
||||
// Atomic values.
|
||||
Token::Bool(b) => Expr::Lit(Lit::Bool(b)),
|
||||
Token::Number(f) => Expr::Lit(Lit::Float(f)),
|
||||
Token::Int(i) => Expr::Lit(Lit::Int(i)),
|
||||
Token::Float(f) => Expr::Lit(Lit::Float(f)),
|
||||
Token::Length(l) => Expr::Lit(Lit::Length(l)),
|
||||
Token::Percent(p) => Expr::Lit(Lit::Percent(p)),
|
||||
Token::Hex(hex) => Expr::Lit(Lit::Color(color(p, hex, start))),
|
||||
Token::Str(token) => Expr::Lit(Lit::Str(string(p, token))),
|
||||
|
||||
|
@ -57,13 +57,13 @@ fn Id(ident: &str) -> Expr {
|
||||
fn Bool(b: bool) -> Expr {
|
||||
Expr::Lit(Lit::Bool(b))
|
||||
}
|
||||
fn _Int(int: i64) -> Expr {
|
||||
fn Int(int: i64) -> Expr {
|
||||
Expr::Lit(Lit::Int(int))
|
||||
}
|
||||
fn Float(float: f64) -> Expr {
|
||||
Expr::Lit(Lit::Float(float))
|
||||
}
|
||||
fn _Percent(percent: f64) -> Expr {
|
||||
fn Percent(percent: f64) -> Expr {
|
||||
Expr::Lit(Lit::Percent(percent))
|
||||
}
|
||||
fn Len(length: Length) -> Expr {
|
||||
@ -334,7 +334,7 @@ fn test_parse_function_names() {
|
||||
t!("[ f]" => F!("f"));
|
||||
|
||||
// An invalid name.
|
||||
e!("[12]" => s(1, 3, "expected function name, found number"));
|
||||
e!("[12]" => s(1, 3, "expected function name, found integer"));
|
||||
e!("[ 🌎]" => s(3, 7, "expected function name, found invalid token"));
|
||||
}
|
||||
|
||||
@ -345,7 +345,7 @@ fn test_parse_chaining() {
|
||||
t!("[box >> pad: 1pt][Hi]" => F!("box"; Tree![
|
||||
F!("pad"; Len(Length::pt(1.0)), Tree!(T("Hi")))
|
||||
]));
|
||||
t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Float(400.0), Tree![
|
||||
t!("[bold: 400, >> emph >> sub: 1cm]" => F!("bold"; Int(400), Tree![
|
||||
F!("emph"; Tree!(F!("sub"; Len(Length::cm(1.0)))))
|
||||
]));
|
||||
|
||||
@ -374,7 +374,7 @@ fn test_parse_colon_starting_func_args() {
|
||||
|
||||
#[test]
|
||||
fn test_parse_function_bodies() {
|
||||
t!("[val: 1][*Hi*]" => F!("val"; Float(1.0), Tree![B, T("Hi"), B]));
|
||||
t!("[val: 1][*Hi*]" => F!("val"; Int(1), Tree![B, T("Hi"), B]));
|
||||
e!(" [val][ */]" => s(8, 10, "unexpected end of block comment"));
|
||||
|
||||
// Raw in body.
|
||||
@ -406,7 +406,7 @@ fn test_parse_values() {
|
||||
v!("false" => Bool(false));
|
||||
v!("1.0e-4" => Float(1e-4));
|
||||
v!("3.14" => Float(3.14));
|
||||
v!("50%" => Float(0.5));
|
||||
v!("50%" => Percent(50.0));
|
||||
v!("4.5cm" => Len(Length::cm(4.5)));
|
||||
v!("12e1pt" => Len(Length::pt(12e1)));
|
||||
v!("#f7a20500" => Color(RgbaColor::new(0xf7, 0xa2, 0x05, 0x00)));
|
||||
@ -440,43 +440,43 @@ fn test_parse_expressions() {
|
||||
v!("(hi)" => Id("hi"));
|
||||
|
||||
// Operations.
|
||||
v!("-1" => Unary(Neg, Float(1.0)));
|
||||
v!("-- 1" => Unary(Neg, Unary(Neg, Float(1.0))));
|
||||
v!("-1" => Unary(Neg, Int(1)));
|
||||
v!("-- 1" => Unary(Neg, Unary(Neg, Int(1))));
|
||||
v!("3.2in + 6pt" => Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))));
|
||||
v!("5 - 0.01" => Binary(Sub, Float(5.0), Float(0.01)));
|
||||
v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Float(2.0)));
|
||||
v!("5 - 0.01" => Binary(Sub, Int(5), Float(0.01)));
|
||||
v!("(3mm * 2)" => Binary(Mul, Len(Length::mm(3.0)), Int(2)));
|
||||
v!("12e-3cm/1pt" => Binary(Div, Len(Length::cm(12e-3)), Len(Length::pt(1.0))));
|
||||
|
||||
// More complex.
|
||||
v!("(3.2in + 6pt)*(5/2-1)" => Binary(
|
||||
Mul,
|
||||
Binary(Add, Len(Length::inches(3.2)), Len(Length::pt(6.0))),
|
||||
Binary(Sub, Binary(Div, Float(5.0), Float(2.0)), Float(1.0))
|
||||
Binary(Sub, Binary(Div, Int(5), Int(2)), Int(1))
|
||||
));
|
||||
v!("(6.3E+2+4* - 3.2pt)/2" => Binary(
|
||||
Div,
|
||||
Binary(Add, Float(6.3e2), Binary(
|
||||
Mul,
|
||||
Float(4.0),
|
||||
Int(4),
|
||||
Unary(Neg, Len(Length::pt(3.2)))
|
||||
)),
|
||||
Float(2.0)
|
||||
Int(2)
|
||||
));
|
||||
|
||||
// Associativity of multiplication and division.
|
||||
v!("3/4*5" => Binary(Mul, Binary(Div, Float(3.0), Float(4.0)), Float(5.0)));
|
||||
v!("3/4*5" => Binary(Mul, Binary(Div, Int(3), Int(4)), Int(5)));
|
||||
|
||||
// Spanned.
|
||||
ts!("[val: 1 + 3]" => s(0, 12, F!(
|
||||
s(1, 4, "val"); s(6, 11, Binary(
|
||||
s(8, 9, Add),
|
||||
s(6, 7, Float(1.0)),
|
||||
s(10, 11, Float(3.0))
|
||||
s(6, 7, Int(1)),
|
||||
s(10, 11, Int(3))
|
||||
))
|
||||
)));
|
||||
|
||||
// Span of parenthesized expression contains parens.
|
||||
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Float(1.0)))));
|
||||
ts!("[val: (1)]" => s(0, 10, F!(s(1, 4, "val"); s(6, 9, Int(1)))));
|
||||
|
||||
// Invalid expressions.
|
||||
v!("4pt--" => Len(Length::pt(4.0)));
|
||||
@ -494,8 +494,8 @@ fn test_parse_dicts() {
|
||||
v!("(false)" => Bool(false));
|
||||
v!("(true,)" => Dict![Bool(true)]);
|
||||
v!("(key=val)" => Dict!["key" => Id("val")]);
|
||||
v!("(1, 2)" => Dict![Float(1.0), Float(2.0)]);
|
||||
v!("(1, key=\"value\")" => Dict![Float(1.0), "key" => Str("value")]);
|
||||
v!("(1, 2)" => Dict![Int(1), Int(2)]);
|
||||
v!("(1, key=\"value\")" => Dict![Int(1), "key" => Str("value")]);
|
||||
|
||||
// Decorations.
|
||||
d!("[val: key=hi]" => s(6, 9, DictKey));
|
||||
@ -513,7 +513,7 @@ fn test_parse_dicts() {
|
||||
#[test]
|
||||
fn test_parse_dicts_compute_func_calls() {
|
||||
v!("empty()" => Call!("empty"));
|
||||
v!("add ( 1 , 2 )" => Call!("add"; Float(1.0), Float(2.0)));
|
||||
v!("add ( 1 , 2 )" => Call!("add"; Int(1), Int(2)));
|
||||
v!("items(\"fire\", #f93a6d)" => Call!("items";
|
||||
Str("fire"), Color(RgbaColor::new(0xf9, 0x3a, 0x6d, 0xff))
|
||||
));
|
||||
@ -522,7 +522,7 @@ fn test_parse_dicts_compute_func_calls() {
|
||||
v!("css(1pt, rgb(90, 102, 254), \"solid\")" => Call!(
|
||||
"css";
|
||||
Len(Length::pt(1.0)),
|
||||
Call!("rgb"; Float(90.0), Float(102.0), Float(254.0)),
|
||||
Call!("rgb"; Int(90), Int(102), Int(254)),
|
||||
Str("solid"),
|
||||
));
|
||||
|
||||
@ -539,10 +539,10 @@ fn test_parse_dicts_compute_func_calls() {
|
||||
fn test_parse_dicts_nested() {
|
||||
v!("(1, ( ab=(), d = (3, 14pt) )), false" =>
|
||||
Dict![
|
||||
Float(1.0),
|
||||
Int(1),
|
||||
Dict!(
|
||||
"ab" => Dict![],
|
||||
"d" => Dict!(Float(3.0), Len(Length::pt(14.0))),
|
||||
"d" => Dict!(Int(3), Len(Length::pt(14.0))),
|
||||
),
|
||||
],
|
||||
Bool(false),
|
||||
@ -576,7 +576,7 @@ fn test_parse_dicts_errors() {
|
||||
s(10, 11, "expected value, found equals sign"));
|
||||
|
||||
// Unexpected equals sign.
|
||||
v!("z=y=4" => "z" => Id("y"), Float(4.0));
|
||||
v!("z=y=4" => "z" => Id("y"), Int(4));
|
||||
e!("[val: z=y=4]" =>
|
||||
s(9, 9, "expected comma"),
|
||||
s(9, 10, "expected value, found equals sign"));
|
||||
|
@ -87,8 +87,8 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
'*' if self.mode == Body => Token::Star,
|
||||
'_' if self.mode == Body => Token::Underscore,
|
||||
'#' if self.mode == Body => Token::Hashtag,
|
||||
'~' if self.mode == Body => Token::NonBreakingSpace,
|
||||
'`' if self.mode == Body => self.read_raw(),
|
||||
'~' if self.mode == Body => Token::Text("\u{00A0}"),
|
||||
'\\' if self.mode == Body => self.read_escaped(),
|
||||
|
||||
// Syntactic elements in headers.
|
||||
@ -273,10 +273,12 @@ impl Debug for Tokens<'_> {
|
||||
fn parse_expr(text: &str) -> Token<'_> {
|
||||
if let Ok(b) = text.parse::<bool>() {
|
||||
Token::Bool(b)
|
||||
} else if let Ok(int) = text.parse::<i64>() {
|
||||
Token::Int(int)
|
||||
} else if let Ok(num) = text.parse::<f64>() {
|
||||
Token::Number(num)
|
||||
} else if let Some(num) = parse_percent(text) {
|
||||
Token::Number(num / 100.0)
|
||||
Token::Float(num)
|
||||
} else if let Some(percent) = parse_percent(text) {
|
||||
Token::Percent(percent)
|
||||
} else if let Ok(length) = text.parse::<Length>() {
|
||||
Token::Length(length)
|
||||
} else if is_ident(text) {
|
||||
@ -298,10 +300,10 @@ mod tests {
|
||||
use crate::parse::tests::check;
|
||||
|
||||
use Token::{
|
||||
BlockComment as BC, Bool, Chain, Hex, Hyphen as Min, Ident as Id,
|
||||
BlockComment as BC, Bool, Chain, Float, Hex, Hyphen as Min, Ident as Id, Int,
|
||||
LeftBrace as LB, LeftBracket as L, LeftParen as LP, Length as Len,
|
||||
LineComment as LC, Number as Num, Plus, RightBrace as RB, RightBracket as R,
|
||||
RightParen as RP, Slash, Space as S, Star, Text as T, *,
|
||||
LineComment as LC, NonBreakingSpace as Nbsp, Percent, Plus, RightBrace as RB,
|
||||
RightBracket as R, RightParen as RP, Slash, Space as S, Star, Text as T, *,
|
||||
};
|
||||
|
||||
fn Str(string: &str, terminated: bool) -> Token {
|
||||
@ -337,7 +339,7 @@ mod tests {
|
||||
t!(Body, " \n\t \n " => S(2));
|
||||
t!(Body, "\n\r" => S(2));
|
||||
t!(Body, " \r\r\n \x0D" => S(3));
|
||||
t!(Body, "a~b" => T("a"), T("\u{00A0}"), T("b"));
|
||||
t!(Body, "a~b" => T("a"), Nbsp, T("b"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -424,24 +426,24 @@ mod tests {
|
||||
t!(Header, ">main" => Invalid(">main"));
|
||||
t!(Header, "🌓, 🌍," => Invalid("🌓"), Comma, S(0), Invalid("🌍"), Comma);
|
||||
t!(Header, "{abc}" => LB, Id("abc"), RB);
|
||||
t!(Header, "(1,2)" => LP, Num(1.0), Comma, Num(2.0), RP);
|
||||
t!(Header, "(1,2)" => LP, Int(1), Comma, Int(2), RP);
|
||||
t!(Header, "12_pt, 12pt" => Invalid("12_pt"), Comma, S(0), Len(Length::pt(12.0)));
|
||||
t!(Header, "f: arg >> g" => Id("f"), Colon, S(0), Id("arg"), S(0), Chain, S(0), Id("g"));
|
||||
t!(Header, "=3.14" => Equals, Num(3.14));
|
||||
t!(Header, "=3.14" => Equals, Float(3.14));
|
||||
t!(Header, "arg, _b, _1" => Id("arg"), Comma, S(0), Id("_b"), Comma, S(0), Id("_1"));
|
||||
t!(Header, "a:b" => Id("a"), Colon, Id("b"));
|
||||
t!(Header, "(){}:=," => LP, RP, LB, RB, Colon, Equals, Comma);
|
||||
t!(Body, "c=d, " => T("c=d,"), S(0));
|
||||
t!(Body, "a: b" => T("a:"), S(0), T("b"));
|
||||
t!(Header, "a: true, x=1" => Id("a"), Colon, S(0), Bool(true), Comma, S(0),
|
||||
Id("x"), Equals, Num(1.0));
|
||||
Id("x"), Equals, Int(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tokenize_numeric_values() {
|
||||
t!(Header, "12.3e5" => Num(12.3e5));
|
||||
t!(Header, "120%" => Num(1.2));
|
||||
t!(Header, "12e4%" => Num(1200.0));
|
||||
t!(Header, "12.3e5" => Float(12.3e5));
|
||||
t!(Header, "120%" => Percent(120.0));
|
||||
t!(Header, "12e4%" => Percent(120000.0));
|
||||
t!(Header, "1e5in" => Len(Length::inches(100000.0)));
|
||||
t!(Header, "2.3cm" => Len(Length::cm(2.3)));
|
||||
t!(Header, "02.4mm" => Len(Length::mm(2.4)));
|
||||
@ -456,7 +458,7 @@ mod tests {
|
||||
t!(Header, "\"hello" => Str("hello", false));
|
||||
t!(Header, "\"hello world\"" => Str("hello world", true));
|
||||
t!(Header, "\"hello\nworld\"" => Str("hello\nworld", true));
|
||||
t!(Header, r#"1"hello\nworld"false"# => Num(1.0), Str("hello\\nworld", true), Bool(false));
|
||||
t!(Header, r#"1"hello\nworld"false"# => Int(1), Str("hello\\nworld", true), Bool(false));
|
||||
t!(Header, r#""a\"bc""# => Str(r#"a\"bc"#, true));
|
||||
t!(Header, r#""a\\"bc""# => Str(r#"a\\"#, true), Id("bc"), Str("", false));
|
||||
t!(Header, r#""a\tbc"# => Str("a\\tbc", false));
|
||||
@ -466,12 +468,12 @@ mod tests {
|
||||
#[test]
|
||||
fn tokenize_math() {
|
||||
t!(Header, "12e-3in" => Len(Length::inches(12e-3)));
|
||||
t!(Header, "-1" => Min, Num(1.0));
|
||||
t!(Header, "--1" => Min, Min, Num(1.0));
|
||||
t!(Header, "- 1" => Min, S(0), Num(1.0));
|
||||
t!(Header, "-1" => Min, Int(1));
|
||||
t!(Header, "--1" => Min, Min, Int(1));
|
||||
t!(Header, "- 1" => Min, S(0), Int(1));
|
||||
t!(Header, "6.1cm + 4pt,a=1*2" => Len(Length::cm(6.1)), S(0), Plus, S(0), Len(Length::pt(4.0)),
|
||||
Comma, Id("a"), Equals, Num(1.0), Star, Num(2.0));
|
||||
t!(Header, "(5 - 1) / 2.1" => LP, Num(5.0), S(0), Min, S(0), Num(1.0), RP,
|
||||
S(0), Slash, S(0), Num(2.1));
|
||||
Comma, Id("a"), Equals, Int(1), Star, Int(2));
|
||||
t!(Header, "(5 - 1) / 2.1" => LP, Int(5), S(0), Min, S(0), Int(1), RP,
|
||||
S(0), Slash, S(0), Float(2.1));
|
||||
}
|
||||
}
|
||||
|
78
src/style.rs
78
src/style.rs
@ -2,8 +2,8 @@
|
||||
|
||||
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
|
||||
|
||||
use crate::geom::{Insets, Sides, Size};
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::geom::{Insets, Linear, Sides, Size};
|
||||
use crate::length::Length;
|
||||
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||
|
||||
/// Defines properties of pages and text.
|
||||
@ -28,37 +28,35 @@ pub struct TextStyle {
|
||||
/// Whether the emphasis toggle is active or inactive. This determines
|
||||
/// whether the next `_` makes italic or non-italic.
|
||||
pub emph: bool,
|
||||
/// The base font size.
|
||||
pub base_font_size: f64,
|
||||
/// The font scale to apply on the base font size.
|
||||
pub font_scale: f64,
|
||||
/// The word spacing (as a multiple of the font size).
|
||||
pub word_spacing_scale: f64,
|
||||
/// The line spacing (as a multiple of the font size).
|
||||
pub line_spacing_scale: f64,
|
||||
/// The paragraphs spacing (as a multiple of the font size).
|
||||
pub paragraph_spacing_scale: f64,
|
||||
/// The font size.
|
||||
pub font_size: FontSize,
|
||||
/// The word spacing (relative to the the font size).
|
||||
pub word_spacing: Linear,
|
||||
/// The line spacing (relative to the the font size).
|
||||
pub line_spacing: Linear,
|
||||
/// The paragraphs spacing (relative to the the font size).
|
||||
pub par_spacing: Linear,
|
||||
}
|
||||
|
||||
impl TextStyle {
|
||||
/// The scaled font size.
|
||||
/// The absolute font size.
|
||||
pub fn font_size(&self) -> f64 {
|
||||
self.base_font_size * self.font_scale
|
||||
self.font_size.eval()
|
||||
}
|
||||
|
||||
/// The absolute word spacing.
|
||||
pub fn word_spacing(&self) -> f64 {
|
||||
self.word_spacing_scale * self.font_size()
|
||||
self.word_spacing.eval(self.font_size())
|
||||
}
|
||||
|
||||
/// The absolute line spacing.
|
||||
pub fn line_spacing(&self) -> f64 {
|
||||
(self.line_spacing_scale - 1.0) * self.font_size()
|
||||
self.line_spacing.eval(self.font_size())
|
||||
}
|
||||
|
||||
/// The absolute paragraph spacing.
|
||||
pub fn paragraph_spacing(&self) -> f64 {
|
||||
(self.paragraph_spacing_scale - 1.0) * self.font_size()
|
||||
self.par_spacing.eval(self.font_size())
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,15 +83,41 @@ impl Default for TextStyle {
|
||||
},
|
||||
strong: false,
|
||||
emph: false,
|
||||
base_font_size: Length::pt(11.0).as_raw(),
|
||||
font_scale: 1.0,
|
||||
word_spacing_scale: 0.25,
|
||||
line_spacing_scale: 1.2,
|
||||
paragraph_spacing_scale: 1.5,
|
||||
font_size: FontSize::abs(Length::pt(11.0).as_raw()),
|
||||
word_spacing: Linear::rel(0.25),
|
||||
line_spacing: Linear::rel(0.2),
|
||||
par_spacing: Linear::rel(0.5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The font size, defined by base and scale.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FontSize {
|
||||
/// The base font size, updated whenever the font size is set absolutely.
|
||||
pub base: f64,
|
||||
/// The scale to apply on the base font size, updated when the font size
|
||||
/// is set relatively.
|
||||
pub scale: Linear,
|
||||
}
|
||||
|
||||
impl FontSize {
|
||||
/// Create a new font size.
|
||||
pub fn new(base: f64, scale: Linear) -> Self {
|
||||
Self { base, scale }
|
||||
}
|
||||
|
||||
/// Create a new font size with the given `base` and a scale of `1.0`.
|
||||
pub fn abs(base: f64) -> Self {
|
||||
Self::new(base, Linear::rel(1.0))
|
||||
}
|
||||
|
||||
/// Compute the absolute font size.
|
||||
pub fn eval(&self) -> f64 {
|
||||
self.scale.eval(self.base)
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the size and margins of a page.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct PageStyle {
|
||||
@ -103,7 +127,7 @@ pub struct PageStyle {
|
||||
pub size: Size,
|
||||
/// The amount of white space in the order [left, top, right, bottom]. If a
|
||||
/// side is set to `None`, the default for the paper class is used.
|
||||
pub margins: Sides<Option<ScaleLength>>,
|
||||
pub margins: Sides<Option<Linear>>,
|
||||
}
|
||||
|
||||
impl PageStyle {
|
||||
@ -121,10 +145,10 @@ impl PageStyle {
|
||||
let Size { width, height } = self.size;
|
||||
let default = self.class.default_margins();
|
||||
Insets {
|
||||
x0: -self.margins.left.unwrap_or(default.left).raw_scaled(width),
|
||||
y0: -self.margins.top.unwrap_or(default.top).raw_scaled(height),
|
||||
x1: -self.margins.right.unwrap_or(default.right).raw_scaled(width),
|
||||
y1: -self.margins.bottom.unwrap_or(default.bottom).raw_scaled(height),
|
||||
x0: -self.margins.left.unwrap_or(default.left).eval(width),
|
||||
y0: -self.margins.top.unwrap_or(default.top).eval(height),
|
||||
x1: -self.margins.right.unwrap_or(default.right).eval(width),
|
||||
y1: -self.margins.bottom.unwrap_or(default.bottom).eval(height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,15 @@ pub enum Lit {
|
||||
Int(i64),
|
||||
/// A floating-point literal: `1.2`, `10e-4`.
|
||||
Float(f64),
|
||||
/// A percent literal: `50%`.
|
||||
Percent(f64),
|
||||
/// A length literal: `12pt`, `3cm`.
|
||||
Length(Length),
|
||||
/// A percent literal: `50%`.
|
||||
///
|
||||
/// Note: `50%` is represented as `50.0` here, but as `0.5` in the
|
||||
/// corresponding [value].
|
||||
///
|
||||
/// [value]: ../../eval/enum.Value.html#variant.Relative
|
||||
Percent(f64),
|
||||
/// A color literal: `#ffccee`.
|
||||
Color(RgbaColor),
|
||||
/// A string literal: `"hello!"`.
|
||||
@ -42,10 +47,10 @@ impl Lit {
|
||||
match *self {
|
||||
Lit::Ident(ref i) => Value::Ident(i.clone()),
|
||||
Lit::Bool(b) => Value::Bool(b),
|
||||
Lit::Int(i) => Value::Number(i as f64),
|
||||
Lit::Float(f) => Value::Number(f as f64),
|
||||
Lit::Percent(p) => Value::Number(p as f64 / 100.0),
|
||||
Lit::Length(l) => Value::Length(l),
|
||||
Lit::Int(i) => Value::Int(i),
|
||||
Lit::Float(f) => Value::Float(f),
|
||||
Lit::Length(l) => Value::Length(l.as_raw()),
|
||||
Lit::Percent(p) => Value::Relative(p / 100.0),
|
||||
Lit::Color(c) => Value::Color(c),
|
||||
Lit::Str(ref s) => Value::Str(s.clone()),
|
||||
Lit::Dict(ref d) => Value::Dict(d.eval(ctx, f).await),
|
||||
|
@ -5,79 +5,91 @@ use crate::length::Length;
|
||||
/// A minimal semantic entity of source code.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum Token<'s> {
|
||||
/// One or more whitespace characters. The contained `usize` denotes the
|
||||
/// number of newlines that were contained in the whitespace.
|
||||
/// One or more whitespace characters.
|
||||
///
|
||||
/// The contained `usize` denotes the number of newlines that were contained
|
||||
/// in the whitespace.
|
||||
Space(usize),
|
||||
/// A consecutive non-markup string.
|
||||
Text(&'s str),
|
||||
|
||||
/// A line comment with inner string contents `//<str>\n`.
|
||||
LineComment(&'s str),
|
||||
/// A block comment with inner string contents `/*<str>*/`. The comment
|
||||
/// can contain nested block comments.
|
||||
/// A block comment with inner string contents `/*<str>*/`.
|
||||
///
|
||||
/// The comment can contain nested block comments.
|
||||
BlockComment(&'s str),
|
||||
|
||||
/// A star. It can appear in a function header where it signifies the
|
||||
/// multiplication of expressions or the body where it modifies the styling.
|
||||
/// A star: `*`.
|
||||
Star,
|
||||
/// An underscore in body-text.
|
||||
/// An underscore: `_`.
|
||||
Underscore,
|
||||
/// A backslash followed by whitespace in text.
|
||||
/// A backslash followed by whitespace: `\`.
|
||||
Backslash,
|
||||
/// A hashtag indicating a section heading.
|
||||
/// A hashtag indicating a section heading: `#`.
|
||||
Hashtag,
|
||||
/// A raw block.
|
||||
/// A non-breaking space: `~`.
|
||||
NonBreakingSpace,
|
||||
/// A raw block: `` `...` ``.
|
||||
Raw(TokenRaw<'s>),
|
||||
/// A unicode escape sequence.
|
||||
/// A unicode escape sequence: `\u{1F5FA}`.
|
||||
UnicodeEscape(TokenUnicodeEscape<'s>),
|
||||
|
||||
/// A left bracket starting a function invocation or body: `[`.
|
||||
/// A left bracket: `[`.
|
||||
LeftBracket,
|
||||
/// A right bracket ending a function invocation or body: `]`.
|
||||
/// A right bracket: `]`.
|
||||
RightBracket,
|
||||
/// A left brace indicating the start of content: `{`.
|
||||
/// A left brace: `{`.
|
||||
LeftBrace,
|
||||
/// A right brace indicating the end of content: `}`.
|
||||
/// A right brace: `}`.
|
||||
RightBrace,
|
||||
/// A left parenthesis in a function header: `(`.
|
||||
/// A left parenthesis: `(`.
|
||||
LeftParen,
|
||||
/// A right parenthesis in a function header: `)`.
|
||||
/// A right parenthesis: `)`.
|
||||
RightParen,
|
||||
|
||||
/// A colon in a function header: `:`.
|
||||
/// A colon: `:`.
|
||||
Colon,
|
||||
/// A comma in a function header: `,`.
|
||||
/// A comma: `,`.
|
||||
Comma,
|
||||
/// An equals sign in a function header: `=`.
|
||||
/// An equals sign: `=`.
|
||||
Equals,
|
||||
/// A double forward chevron in a function header: `>>`.
|
||||
/// A double forward chevron: `>>`.
|
||||
Chain,
|
||||
/// A plus in a function header, signifying the addition of expressions.
|
||||
/// A plus: `+`.
|
||||
Plus,
|
||||
/// A hyphen in a function header, signifying the subtraction of
|
||||
/// expressions.
|
||||
/// A hyphen: `-`.
|
||||
Hyphen,
|
||||
/// A slash in a function header, signifying the division of expressions.
|
||||
/// A slash: `/`.
|
||||
Slash,
|
||||
|
||||
/// An identifier in a function header: `center`.
|
||||
/// An identifier: `center`.
|
||||
Ident(&'s str),
|
||||
/// A boolean in a function header: `true | false`.
|
||||
/// A boolean: `true`, `false`.
|
||||
Bool(bool),
|
||||
/// A number in a function header: `3.14`.
|
||||
Number(f64),
|
||||
/// A length in a function header: `12pt`.
|
||||
/// An integer: `120`.
|
||||
Int(i64),
|
||||
/// A floating-point number: `1.2`, `10e-4`.
|
||||
Float(f64),
|
||||
/// A length: `12pt`, `3cm`.
|
||||
Length(Length),
|
||||
/// A hex value in a function header: `#20d82a`.
|
||||
/// A percentage: `50%`.
|
||||
///
|
||||
/// Note: `50%` is represented as `50.0` here, as in the corresponding
|
||||
/// [literal].
|
||||
///
|
||||
/// [literal]: ../ast/enum.Lit.html#variant.Percent
|
||||
Percent(f64),
|
||||
/// A hex value: `#20d82a`.
|
||||
Hex(&'s str),
|
||||
/// A quoted string in a function header: `"..."`.
|
||||
/// A quoted string: `"..."`.
|
||||
Str(TokenStr<'s>),
|
||||
|
||||
/// Things that are not valid in the context they appeared in.
|
||||
Invalid(&'s str),
|
||||
}
|
||||
|
||||
/// A quoted string in a function header: `"..."`.
|
||||
/// A quoted string: `"..."`.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct TokenStr<'s> {
|
||||
/// The string inside the quotes.
|
||||
@ -90,7 +102,7 @@ pub struct TokenStr<'s> {
|
||||
pub terminated: bool,
|
||||
}
|
||||
|
||||
/// A unicode escape sequence.
|
||||
/// A unicode escape sequence: `\u{1F5FA}`.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct TokenUnicodeEscape<'s> {
|
||||
/// The escape sequence between two braces.
|
||||
@ -99,7 +111,7 @@ pub struct TokenUnicodeEscape<'s> {
|
||||
pub terminated: bool,
|
||||
}
|
||||
|
||||
/// A raw block.
|
||||
/// A raw block: `` `...` ``.
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub struct TokenRaw<'s> {
|
||||
/// The raw text between the backticks.
|
||||
@ -111,7 +123,7 @@ pub struct TokenRaw<'s> {
|
||||
}
|
||||
|
||||
impl<'s> Token<'s> {
|
||||
/// The natural-language name for this token for use in error messages.
|
||||
/// The natural-language name of this token for use in error messages.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Space(_) => "space",
|
||||
@ -124,6 +136,7 @@ impl<'s> Token<'s> {
|
||||
Self::Underscore => "underscore",
|
||||
Self::Backslash => "backslash",
|
||||
Self::Hashtag => "hashtag",
|
||||
Self::NonBreakingSpace => "non-breaking space",
|
||||
Self::Raw { .. } => "raw block",
|
||||
Self::UnicodeEscape { .. } => "unicode escape sequence",
|
||||
|
||||
@ -144,8 +157,10 @@ impl<'s> Token<'s> {
|
||||
|
||||
Self::Ident(_) => "identifier",
|
||||
Self::Bool(_) => "bool",
|
||||
Self::Number(_) => "number",
|
||||
Self::Int(_) => "integer",
|
||||
Self::Float(_) => "float",
|
||||
Self::Length(_) => "length",
|
||||
Self::Percent(_) => "percentage",
|
||||
Self::Hex(_) => "hex value",
|
||||
Self::Str { .. } => "string",
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user