Unify font and page functions 💕

- Removes font weight and width warnings for now, will be added again later
- Adds a bit hacky get_first function for tuples, will be refactored soon anyway
This commit is contained in:
Laurenz 2020-08-02 11:06:45 +02:00
parent 659248d52f
commit efb78831a7
9 changed files with 83 additions and 171 deletions

View File

@ -3,24 +3,33 @@ use crate::length::ScaleLength;
use super::*;
function! {
/// `font.family`: Set the font family.
/// `font`: Configure the font.
#[derive(Debug, Clone, PartialEq)]
pub struct FontFamilyFunc {
pub struct FontFunc {
body: Option<SyntaxModel>,
size: Option<ScaleLength>,
style: Option<FontStyle>,
weight: Option<FontWeight>,
width: Option<FontWidth>,
list: Vec<String>,
classes: Vec<(String, Vec<String>)>,
}
parse(header, body, ctx, f) {
let size = header.args.pos.get_first::<ScaleLength>(&mut f.diagnostics);
let style = header.args.key.get::<FontStyle>(&mut f.diagnostics, "style");
let weight = header.args.key.get::<FontWeight>(&mut f.diagnostics, "weight");
let width = header.args.key.get::<FontWidth>(&mut f.diagnostics, "width");
let list = header.args.pos.get_all::<StringLike>(&mut f.diagnostics)
.map(|s| s.0.to_lowercase())
.collect();
let tuples: Vec<_> = header.args.key
let classes = header.args.key
.get_all::<String, Tuple>(&mut f.diagnostics)
.collect();
let classes = tuples.into_iter()
.collect::<Vec<_>>()
.into_iter()
.map(|(class, mut tuple)| {
let fallback = tuple.get_all::<StringLike>(&mut f.diagnostics)
.map(|s| s.0.to_lowercase())
@ -29,140 +38,41 @@ function! {
})
.collect();
FontFamilyFunc {
FontFunc {
body: body!(opt: body, ctx, f),
size,
list,
classes,
style,
weight,
width,
}
}
layout(self, ctx, f) {
styled(&self.body, ctx, Some(()),
|s, _| {
|t, _| {
self.size.with(|s| match s {
ScaleLength::Absolute(length) => {
t.base_font_size = length.as_raw();
t.font_scale = 1.0;
}
ScaleLength::Scaled(scale) => t.font_scale = scale,
});
self.style.with(|s| t.variant.style = s);
self.weight.with(|w| t.variant.weight = w);
self.width.with(|w| t.variant.width = w);
if !self.list.is_empty() {
*s.fallback.list_mut() = self.list.clone();
*t.fallback.list_mut() = self.list.clone();
}
for (class, fallback) in &self.classes {
s.fallback.set_class_list(class.clone(), fallback.clone());
t.fallback.set_class_list(class.clone(), fallback.clone());
}
s.fallback.flatten();
t.fallback.flatten();
})
}
}
function! {
/// `font.style`: Set the font style (normal / italic).
#[derive(Debug, Clone, PartialEq)]
pub struct FontStyleFunc {
body: Option<SyntaxModel>,
style: Option<FontStyle>,
}
parse(header, body, ctx, f) {
FontStyleFunc {
body: body!(opt: body, ctx, f),
style: header.args.pos.get::<FontStyle>(&mut f.diagnostics)
.or_missing(&mut f.diagnostics, header.name.span, "style"),
}
}
layout(self, ctx, f) {
styled(&self.body, ctx, self.style, |t, s| t.variant.style = s)
}
}
function! {
/// `font.weight`: Set text with a given weight.
#[derive(Debug, Clone, PartialEq)]
pub struct FontWeightFunc {
body: Option<SyntaxModel>,
weight: Option<FontWeight>,
}
parse(header, body, ctx, f) {
let body = body!(opt: body, ctx, f);
let weight = header.args.pos.get::<Spanned<(FontWeight, bool)>>(&mut f.diagnostics)
.map(|Spanned { v: (weight, is_clamped), span }| {
if is_clamped {
warning!(
@f, span,
"weight should be between 100 and 900, clamped to {}",
weight.0,
);
}
weight
})
.or_missing(&mut f.diagnostics, header.name.span, "weight");
FontWeightFunc { body, weight }
}
layout(self, ctx, f) {
styled(&self.body, ctx, self.weight, |t, w| t.variant.weight = w)
}
}
function! {
/// `font.width`: Set text with a given width.
#[derive(Debug, Clone, PartialEq)]
pub struct FontWidthFunc {
body: Option<SyntaxModel>,
width: Option<FontWidth>,
}
parse(header, body, ctx, f) {
let body = body!(opt: body, ctx, f);
let width = header.args.pos.get::<Spanned<(FontWidth, bool)>>(&mut f.diagnostics)
.map(|Spanned { v: (width, is_clamped), span }| {
if is_clamped {
warning!(
@f, span,
"width should be between 1 and 9, clamped to {}",
width.to_number(),
);
}
width
})
.or_missing(&mut f.diagnostics, header.name.span, "width");
FontWidthFunc { body, width }
}
layout(self, ctx, f) {
styled(&self.body, ctx, self.width, |t, w| t.variant.width = w)
}
}
function! {
/// `font.size`: Sets the font size.
#[derive(Debug, Clone, PartialEq)]
pub struct FontSizeFunc {
body: Option<SyntaxModel>,
size: Option<ScaleLength>,
}
parse(header, body, ctx, f) {
FontSizeFunc {
body: body!(opt: body, ctx, f),
size: header.args.pos.get::<ScaleLength>(&mut f.diagnostics)
.or_missing(&mut f.diagnostics, header.name.span, "size")
}
}
layout(self, ctx, f) {
styled(&self.body, ctx, self.size, |t, s| {
match s {
ScaleLength::Absolute(length) => {
t.base_font_size = length.as_raw();
t.font_scale = 1.0;
}
ScaleLength::Scaled(scale) => t.font_scale = scale,
}
})
}
}

View File

@ -16,11 +16,7 @@ pub fn std() -> Scope {
std.add::<ValFunc>("val");
// Font setup
std.add::<FontFamilyFunc>("font.family");
std.add::<FontStyleFunc>("font.style");
std.add::<FontWeightFunc>("font.weight");
std.add::<FontWidthFunc>("font.width");
std.add::<FontSizeFunc>("font.size");
std.add::<FontFunc>("font");
std.add_with_meta::<ContentSpacingFunc>("word.spacing", ContentKind::Word);
// Layout
@ -40,8 +36,7 @@ pub fn std() -> Scope {
std.add_with_meta::<SpacingFunc>("v", Some(Vertical));
// Page setup
std.add::<PageSizeFunc>("page.size");
std.add::<PageMarginsFunc>("page.margins");
std.add::<PageFunc>("page");
std
}

View File

@ -3,19 +3,21 @@ use crate::paper::{Paper, PaperClass};
use super::*;
function! {
/// `page.size`: Set the size of pages.
/// `page`: Configure pages.
#[derive(Debug, Clone, PartialEq)]
pub struct PageSizeFunc {
pub struct PageFunc {
paper: Option<Paper>,
extents: AxisMap<Length>,
padding: PaddingMap,
flip: bool,
}
parse(header, body, state, f) {
body!(nope: body, f);
PageSizeFunc {
PageFunc {
paper: header.args.pos.get::<Paper>(&mut f.diagnostics),
extents: AxisMap::parse::<ExtentKey>(&mut f.diagnostics, &mut header.args.key),
padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args),
flip: header.args.key.get::<bool>(&mut f.diagnostics, "flip").unwrap_or(false),
}
}
@ -38,27 +40,8 @@ function! {
style.dimensions.swap();
}
vec![SetPageStyle(style)]
}
}
function! {
/// `page.margins`: Sets the page margins.
#[derive(Debug, Clone, PartialEq)]
pub struct PageMarginsFunc {
padding: PaddingMap,
}
parse(header, body, state, f) {
body!(nope: body, f);
PageMarginsFunc {
padding: PaddingMap::parse(&mut f.diagnostics, &mut header.args),
}
}
layout(self, ctx, f) {
let mut style = ctx.style.page;
self.padding.apply(&mut f.diagnostics, ctx.axes, &mut style.margins);
vec![SetPageStyle(style)]
}
}

View File

@ -268,6 +268,25 @@ impl Tuple {
None
}
/// Extract (and remove) the first matching value without removing and
/// generating diagnostics for all previous items that did not match.
pub fn get_first<V: Value>(&mut self, _: &mut Diagnostics) -> Option<V> {
let mut i = 0;
while i < self.items.len() {
let expr = self.items[i].clone();
match V::parse(expr) {
Ok(output) => {
self.items.remove(i);
return Some(output)
}
Err(_) => {},
}
i += 1;
}
None
}
/// Extract and return an iterator over all values that match and generate
/// diagnostics for all items that do not match.
pub fn get_all<'a, V: Value>(&'a mut self, diagnostics: &'a mut Diagnostics)

View File

@ -178,7 +178,7 @@ impl PaddingMap {
pub fn parse(diagnostics: &mut Diagnostics, args: &mut FuncArgs) -> PaddingMap {
let mut map = DedupMap::new();
let all = args.pos.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics);
let all = args.key.get::<Spanned<Defaultable<ScaleLength>>>(diagnostics, "margins");
if let Some(Spanned { v, span }) = all {
map.insert(diagnostics, Spanned { v: (PaddingKey::All, v.into()), span });
}

View File

@ -82,13 +82,22 @@ pub enum FuncArg {
}
/// Extra methods on [`Options`](Option) used for argument parsing.
pub trait OptionExt: Sized {
pub trait OptionExt<T>: Sized {
/// Calls `f` with `val` if this is `Some(val)`.
fn with(self, f: impl FnOnce(T));
/// Add an error about a missing argument `arg` with the given span if the
/// option is `None`.
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self;
}
impl<T> OptionExt for Option<T> {
impl<T> OptionExt<T> for Option<T> {
fn with(self, f: impl FnOnce(T)) {
if let Some(val) = self {
f(val);
}
}
fn or_missing(self, diagnostics: &mut Diagnostics, span: Span, arg: &str) -> Self {
if self.is_none() {
diagnostics.push(error!(span, "missing argument: {}", arg));

View File

@ -143,22 +143,21 @@ impl Value for FontStyle {
/// The additional boolean specifies whether a number was clamped into the range
/// 100 - 900 to make it a valid font weight.
impl Value for (FontWeight, bool) {
impl Value for FontWeight {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
match expr.v {
Expr::Number(weight) => {
let weight = weight.round();
if weight >= 100.0 && weight <= 900.0 {
Ok((FontWeight(weight as u16), false))
Ok(FontWeight(weight as u16))
} else {
let clamped = weight.min(900.0).max(100.0);
Ok((FontWeight(clamped as u16), true))
Ok(FontWeight(clamped as u16))
}
}
Expr::Ident(id) => {
FontWeight::from_name(id.as_str())
.ok_or_else(|| error!("invalid font weight"))
.map(|weight| (weight, false))
}
other => Err(
error!("expected identifier or number, found {}", other.name())
@ -169,22 +168,21 @@ impl Value for (FontWeight, bool) {
/// The additional boolean specifies whether a number was clamped into the range
/// 1 - 9 to make it a valid font width.
impl Value for (FontWidth, bool) {
impl Value for FontWidth {
fn parse(expr: Spanned<Expr>) -> Result<Self, Diagnostic> {
match expr.v {
Expr::Number(width) => {
let width = width.round();
if width >= 1.0 && width <= 9.0 {
Ok((FontWidth::new(width as u16).unwrap(), false))
Ok(FontWidth::new(width as u16).unwrap())
} else {
let clamped = width.min(9.0).max(1.0);
Ok((FontWidth::new(clamped as u16).unwrap(), true))
Ok(FontWidth::new(clamped as u16).unwrap())
}
}
Expr::Ident(id) => {
FontWidth::from_name(id.as_str())
.ok_or_else(|| error!("invalid font width"))
.map(|width| (width, false))
}
other => Err(
error!("expected identifier or number, found {}", other.name())

View File

@ -1,5 +1,4 @@
[page.size: width=450pt, height=300pt]
[page.margins: 1cm]
[page: width=450pt, height=300pt, margins=1cm]
[box][
*Technische Universität Berlin* [n]

View File

@ -1,5 +1,4 @@
[page.size: w=5cm, h=5cm]
[page.margins: 0cm]
[page: w=5cm, h=5cm, margins=0cm]
// Test 1
[box: w=1, h=1, debug=false][