diff --git a/src/func/mod.rs b/src/func/mod.rs index 208e19773..427e5b6ff 100644 --- a/src/func/mod.rs +++ b/src/func/mod.rs @@ -101,7 +101,7 @@ pub enum Command<'a> { Add(Layout), AddMultiple(MultiLayout), - AddSpacing(Size, SpacingKind, GenericAxis), + SpacingFunc(Size, SpacingKind, GenericAxis), FinishLine, FinishSpace, diff --git a/src/layout/tree.rs b/src/layout/tree.rs index e77fd5288..4ed3d82ac 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -44,9 +44,19 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Node::Space => self.layout_space(), Node::Newline => self.layout_paragraph()?, - Node::ToggleItalics => {}, - Node::ToggleBold => {}, - Node::ToggleMonospace => {}, + Node::ToggleItalics => self.style.text.variant.style.toggle(), + Node::ToggleBolder => { + self.style.text.variant.weight.0 += 300 * + if self.style.text.bolder { -1 } else { 1 }; + self.style.text.bolder = !self.style.text.bolder; + } + Node::ToggleMonospace => { + let list = &mut self.style.text.fallback.list; + match list.get(0).map(|s| s.as_str()) { + Some("monospace") => { list.remove(0); }, + _ => list.insert(0, "monospace".to_string()), + } + } Node::Func(func) => self.layout_func(func)?, } @@ -98,7 +108,7 @@ impl<'a, 'p> TreeLayouter<'a, 'p> { Add(layout) => self.layouter.add(layout)?, AddMultiple(layouts) => self.layouter.add_multiple(layouts)?, - AddSpacing(space, kind, axis) => match axis { + SpacingFunc(space, kind, axis) => match axis { Primary => self.layouter.add_primary_spacing(space, kind), Secondary => self.layouter.add_secondary_spacing(space, kind)?, } diff --git a/src/library/align.rs b/src/library/align.rs index 524ada61d..03d905cd1 100644 --- a/src/library/align.rs +++ b/src/library/align.rs @@ -1,16 +1,17 @@ use crate::func::prelude::*; use super::maps::{PosAxisMap, AlignmentKey}; + function! { /// `align`: Aligns content along the layouting axes. #[derive(Debug, PartialEq)] - pub struct Align { + pub struct AlignFunc { body: Option, map: PosAxisMap, } parse(args, body, ctx) { - Align { + AlignFunc { body: parse!(optional: body, ctx), map: PosAxisMap::new(&mut args)?, } @@ -28,7 +29,7 @@ function! { match &self.body { Some(body) => vec![AddMultiple(layout(&body, ctx)?)], - None => vec![Command::SetAlignment(ctx.alignment)], + None => vec![SetAlignment(ctx.alignment)], } } } diff --git a/src/library/boxed.rs b/src/library/boxed.rs index 7333c50f2..a4d059cb9 100644 --- a/src/library/boxed.rs +++ b/src/library/boxed.rs @@ -3,17 +3,18 @@ use smallvec::smallvec; use crate::func::prelude::*; use super::maps::ExtentMap; + function! { /// `box`: Layouts content into a box. #[derive(Debug, PartialEq)] - pub struct Boxed { + pub struct BoxFunc { body: SyntaxTree, map: ExtentMap, debug: Option, } parse(args, body, ctx) { - Boxed { + BoxFunc { body: parse!(optional: body, ctx).unwrap_or(SyntaxTree::new()), map: ExtentMap::new(&mut args, false)?, debug: args.get_key_opt::("debug")?, diff --git a/src/library/direction.rs b/src/library/direction.rs index 7bb11c997..a09920752 100644 --- a/src/library/direction.rs +++ b/src/library/direction.rs @@ -1,16 +1,17 @@ use crate::func::prelude::*; use super::maps::PosAxisMap; + function! { /// `direction`: Sets the directions of the layouting axes. #[derive(Debug, PartialEq)] - pub struct DirectionChange { + pub struct DirectionFunc { body: Option, map: PosAxisMap, } parse(args, body, ctx) { - DirectionChange { + DirectionFunc { body: parse!(optional: body, ctx), map: PosAxisMap::new(&mut args)?, } diff --git a/src/library/mod.rs b/src/library/mod.rs index d4519867d..ac1ac3388 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -12,73 +12,168 @@ pub_use_mod!(align); pub_use_mod!(boxed); pub_use_mod!(direction); + /// Create a scope with all standard functions. pub fn std() -> Scope { let mut std = Scope::new(); - std.add::("align"); - std.add::("box"); - std.add::("direction"); + // Font setup + std.add::("font.family"); + std.add::("font.style"); + std.add::("font.weight"); + std.add::("font.size"); - std.add::("n"); - std.add::("line.break"); - std.add::("par.break"); - std.add::("page.break"); + // Layout + std.add::("align"); + std.add::("direction"); + std.add_with_metadata::("par.spacing", ContentKind::Paragraph); + std.add_with_metadata::("word.spacing", ContentKind::Word); + std.add_with_metadata::("line.spacing", ContentKind::Line); + std.add::("box"); - std.add_with_metadata::("word.spacing", ContentKind::Word); - std.add_with_metadata::("line.spacing", ContentKind::Line); - std.add_with_metadata::("par.spacing", ContentKind::Paragraph); + // Spacing + std.add::("n"); + std.add::("line.break"); + std.add::("par.break"); + std.add::("page.break"); + std.add_with_metadata::("spacing", None); + std.add_with_metadata::("h", Some(Horizontal)); + std.add_with_metadata::("v", Some(Vertical)); - std.add::("page.size"); - std.add::("page.margins"); - - std.add_with_metadata::("spacing", None); - std.add_with_metadata::("h", Some(Horizontal)); - std.add_with_metadata::("v", Some(Vertical)); - - std.add_with_metadata::("font.family", None); - std.add_with_metadata::("mono", Some("monospace".to_string())); - std.add::("font.style"); - std.add::("font.weight"); - std.add::("font.size"); + // Page setup + std.add::("page.size"); + std.add::("page.margins"); std } -function! { - /// `line.break`, `n`: Ends the current line. - #[derive(Debug, Default, PartialEq)] - pub struct LineBreak; +// -------------------------------------------------------------------------- // +// Font setup - parse(default) - layout() { vec![FinishLine] } +function! { + /// `font.family`: Set the font family. + #[derive(Debug, PartialEq)] + pub struct FontFamilyFunc { + body: Option, + family: String, + } + + parse(args, body, ctx, meta) { + FontFamilyFunc { + body: parse!(optional: body, ctx), + family: args.get_pos::()?, + } + } + + layout(self, ctx) { + let mut style = ctx.style.text.clone(); + style.fallback.list = vec![self.family.clone()]; + styled(&self.body, &ctx, style) + } } function! { - /// `par.break`: Ends the current paragraph. - /// - /// self has the same effect as two subsequent newlines. - #[derive(Debug, Default, PartialEq)] - pub struct ParBreak; + /// `font.style`: Set the font style (normal / italic). + #[derive(Debug, PartialEq)] + pub struct FontStyleFunc { + body: Option, + style: FontStyle, + } - parse(default) - layout() { vec![BreakParagraph] } + parse(args, body, ctx) { + FontStyleFunc { + body: parse!(optional: body, ctx), + style: { + let s = args.get_pos::()?; + match FontStyle::from_str(&s) { + Some(style) => style, + None => error!("invalid font style: `{}`", s), + } + } + } + } + + layout(self, ctx) { + let mut style = ctx.style.text.clone(); + style.variant.style = self.style; + styled(&self.body, &ctx, style) + } } function! { - /// `page.break`: Ends the current page. - #[derive(Debug, Default, PartialEq)] - pub struct PageBreak; + /// `font.weight`: Set text with a given weight. + #[derive(Debug, PartialEq)] + pub struct FontWeightFunc { + body: Option, + weight: FontWeight, + } - parse(default) - layout() { vec![BreakPage] } + parse(args, body, ctx, meta) { + FontWeightFunc { + body: parse!(optional: body, ctx), + weight: match args.get_pos::()? { + Expression::Num(weight) => { + let weight = weight.round() as i16; + FontWeight( + if weight < 100 { 100 } + else if weight <= 900 { weight } + else { 900 } + ) + } + Expression::Ident(Ident(s)) => { + match FontWeight::from_str(&s) { + Some(weight) => weight, + None => error!("invalid font weight: `{}`", s), + } + } + _ => error!("expected identifier or number"), + }, + } + } + + layout(self, ctx) { + let mut style = ctx.style.text.clone(); + style.variant.style.toggle(); + styled(&self.body, &ctx, style) + } } +function! { + /// `font.size`: Sets the font size. + #[derive(Debug, PartialEq)] + pub struct FontSizeFunc { + body: Option, + size: ScaleSize, + } + + parse(args, body, ctx) { + FontSizeFunc { + body: parse!(optional: body, ctx), + size: args.get_pos::()?, + } + } + + layout(self, ctx) { + let mut style = ctx.style.text.clone(); + match self.size { + ScaleSize::Absolute(size) => { + style.base_font_size = size; + style.font_scale = 1.0; + } + ScaleSize::Scaled(scale) => style.font_scale = scale, + } + styled(&self.body, &ctx, style) + } +} + +// -------------------------------------------------------------------------- // +// Layout + function! { /// `word.spacing`, `line.spacing`, `par.spacing`: The spacing between /// words, lines or paragraphs as a multiple of the font size. #[derive(Debug, PartialEq)] - pub struct ContentSpacing { + pub struct ContentSpacingFunc { body: Option, content: ContentKind, spacing: f32, @@ -87,7 +182,7 @@ function! { type Meta = ContentKind; parse(args, body, ctx, meta) { - ContentSpacing { + ContentSpacingFunc { body: parse!(optional: body, ctx), content: meta, spacing: args.get_pos::()? as f32, @@ -113,10 +208,81 @@ pub enum ContentKind { Paragraph, } +// -------------------------------------------------------------------------- // +// Spacing + +function! { + /// `line.break`, `n`: Ends the current line. + #[derive(Debug, Default, PartialEq)] + pub struct LineBreakFunc; + + parse(default) + layout() { vec![FinishLine] } +} + +function! { + /// `par.break`: Ends the current paragraph. + /// + /// self has the same effect as two subsequent newlines. + #[derive(Debug, Default, PartialEq)] + pub struct ParBreakFunc; + + parse(default) + layout() { vec![BreakParagraph] } +} + +function! { + /// `page.break`: Ends the current page. + #[derive(Debug, Default, PartialEq)] + pub struct PageBreakFunc; + + parse(default) + layout() { vec![BreakPage] } +} + +function! { + /// `spacing`, `h`, `v`: Adds spacing along an axis. + #[derive(Debug, PartialEq)] + pub struct SpacingFunc { + axis: AxisKey, + spacing: FSize, + } + + type Meta = Option; + + parse(args, body, _, meta) { + parse!(forbidden: body); + + if let Some(axis) = meta { + SpacingFunc { + axis: AxisKey::Specific(axis), + spacing: FSize::from_expr(args.get_pos::>()?)?, + } + } else if let Some(arg) = args.get_key_next() { + let axis = AxisKey::from_ident(&arg.v.key) + .map_err(|_| error!(@unexpected_argument))?; + + let spacing = FSize::from_expr(arg.v.value)?; + SpacingFunc { axis, spacing } + } else { + error!("expected axis and spacing") + } + } + + layout(self, ctx) { + let axis = self.axis.to_generic(ctx.axes); + let spacing = self.spacing.scaled(ctx.style.text.font_size()); + vec![SpacingFunc(spacing, SpacingKind::Hard, axis)] + } +} + +// -------------------------------------------------------------------------- // +// Page setup + function! { /// `page.size`: Set the size of pages. #[derive(Debug, PartialEq)] - pub enum PageSize { + pub enum PageSizeFunc { Paper(Paper, bool), Custom(ExtentMap), } @@ -125,11 +291,11 @@ function! { parse!(forbidden: body); if let Some(name) = args.get_pos_opt::()? { - let landscape = args.get_key_opt::("landscape")? + let flip = args.get_key_opt::("flip")? .unwrap_or(false); - PageSize::Paper(Paper::from_name(name.as_str())?, landscape) + PageSizeFunc::Paper(Paper::from_name(name.as_str())?, flip) } else { - PageSize::Custom(ExtentMap::new(&mut args, true)?) + PageSizeFunc::Custom(ExtentMap::new(&mut args, true)?) } } @@ -137,15 +303,15 @@ function! { let mut style = ctx.style.page; match self { - PageSize::Paper(paper, landscape) => { + PageSizeFunc::Paper(paper, flip) => { style.class = paper.class; style.dimensions = paper.dimensions; - if *landscape { + if *flip { style.dimensions.swap(); } } - PageSize::Custom(map) => { + PageSizeFunc::Custom(map) => { style.class = PaperClass::Custom; let map = map.dedup(ctx.axes)?; @@ -162,13 +328,13 @@ function! { function! { /// `page.margins`: Sets the page margins. #[derive(Debug, PartialEq)] - pub struct PageMargins { + pub struct PageMarginsFunc { map: PaddingMap, } parse(args, body) { parse!(forbidden: body); - PageMargins { + PageMarginsFunc { map: PaddingMap::new(&mut args)?, } } @@ -180,162 +346,8 @@ function! { } } -function! { - /// `spacing`, `h`, `v`: Adds spacing along an axis. - #[derive(Debug, PartialEq)] - pub struct Spacing { - axis: AxisKey, - spacing: FSize, - } - - type Meta = Option; - - parse(args, body, _, meta) { - parse!(forbidden: body); - - if let Some(axis) = meta { - Spacing { - axis: AxisKey::Specific(axis), - spacing: FSize::from_expr(args.get_pos::>()?)?, - } - } else if let Some(arg) = args.get_key_next() { - let axis = AxisKey::from_ident(&arg.v.key) - .map_err(|_| error!(@unexpected_argument))?; - - let spacing = FSize::from_expr(arg.v.value)?; - Spacing { axis, spacing } - } else { - error!("expected axis and spacing") - } - } - - layout(self, ctx) { - let axis = self.axis.to_generic(ctx.axes); - let spacing = self.spacing.scaled(ctx.style.text.font_size()); - vec![AddSpacing(spacing, SpacingKind::Hard, axis)] - } -} - -function! { - /// `font.weight`, `bold`: Set text with a given weight. - #[derive(Debug, PartialEq)] - pub struct SetFontWeight { - body: Option, - weight: FontWeight, - } - - parse(args, body, ctx, meta) { - SetFontWeight { - body: parse!(optional: body, ctx), - weight: match args.get_pos::()? { - Expression::Num(weight) => FontWeight(if weight < 0.0 { - 0 - } else if weight < 1000.0 { - weight.round() as u16 - } else { - 1000 - }), - Expression::Ident(Ident(s)) => { - match FontWeight::from_str(&s) { - Some(weight) => weight, - None => error!("invalid font weight: `{}`", s), - } - } - _ => error!("expected identifier or number"), - }, - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - style.variant.style.toggle(); - styled(&self.body, &ctx, style) - } -} - -function! { - /// `font.style`: Set the font style (normal / italic). - #[derive(Debug, PartialEq)] - pub struct SetFontStyle { - body: Option, - style: FontStyle, - } - - parse(args, body, ctx) { - SetFontStyle { - body: parse!(optional: body, ctx), - style: { - let s = args.get_pos::()?; - match FontStyle::from_str(&s) { - Some(style) => style, - None => error!("invalid font style: `{}`", s), - } - } - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - style.variant.style = self.style; - styled(&self.body, &ctx, style) - } -} - -function! { - /// `font.family`: Set the font family. - #[derive(Debug, PartialEq)] - pub struct FontFamily { - body: Option, - family: String, - } - - type Meta = Option; - - parse(args, body, ctx, meta) { - FontFamily { - body: parse!(optional: body, ctx), - family: if let Some(family) = meta { - family - } else { - args.get_pos::()? - }, - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - style.fallback.list = vec![self.family.clone()]; - styled(&self.body, &ctx, style) - } -} - -function! { - /// `font.size`: Sets the font size. - #[derive(Debug, PartialEq)] - pub struct FontSize { - body: Option, - size: ScaleSize, - } - - parse(args, body, ctx) { - FontSize { - body: parse!(optional: body, ctx), - size: args.get_pos::()?, - } - } - - layout(self, ctx) { - let mut style = ctx.style.text.clone(); - match self.size { - ScaleSize::Absolute(size) => { - style.base_font_size = size; - style.font_scale = 1.0; - } - ScaleSize::Scaled(scale) => style.font_scale = scale, - } - styled(&self.body, &ctx, style) - } -} +// -------------------------------------------------------------------------- // +// Helpers /// Layout the body with the style or update the style if there is no body. fn styled<'a>( diff --git a/src/style.rs b/src/style.rs index ffa10d513..cbe4bf01a 100644 --- a/src/style.rs +++ b/src/style.rs @@ -20,6 +20,9 @@ pub struct TextStyle { pub fallback: FontFallbackTree, /// The selected font variant. pub variant: FontVariant, + /// Whether the bolder toggle is active or inactive. This determines + /// whether the next `*` adds or removes font weight. + pub bolder: bool, /// The base font size. pub base_font_size: Size, /// The font scale to apply on the base font size. @@ -80,6 +83,7 @@ impl Default for TextStyle { style: FontStyle::Normal, weight: FontWeight(400), }, + bolder: false, base_font_size: Size::pt(11.0), font_scale: 1.0, word_spacing_scale: 0.25, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index bf395c2bf..193810c01 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -72,11 +72,11 @@ pub enum Node { Space, /// A line feed. Newline, - /// Indicates that italics were enabled / disabled. + /// Indicates that italics were toggled. ToggleItalics, - /// Indicates that boldface was enabled / disabled. - ToggleBold, - /// Indicates that monospace was enabled / disabled. + /// Indicates that bolder text was toggled. + ToggleBolder, + /// Indicates that monospace was toggled. ToggleMonospace, /// Literal text. Text(String), diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index dcba1d0f4..6eed22356 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -73,7 +73,7 @@ impl<'s> Parser<'s> { // Modifiers. Underscore => self.append_consumed(Node::ToggleItalics, token.span), - Star => self.append_consumed(Node::ToggleBold, token.span), + Star => self.append_consumed(Node::ToggleBolder, token.span), Backtick => self.append_consumed(Node::ToggleMonospace, token.span), // Normal text.