mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Introduce a set of macros for writing functions more concisely 🎁
This commit is contained in:
parent
cff325b520
commit
ecf0ff4d05
129
src/func/helpers.rs
Normal file
129
src/func/helpers.rs
Normal file
@ -0,0 +1,129 @@
|
||||
use super::prelude::*;
|
||||
use std::iter::Peekable;
|
||||
use std::slice::Iter;
|
||||
|
||||
/// Implement the function trait more concisely.
|
||||
#[macro_export]
|
||||
macro_rules! function {
|
||||
(data: $ident:ident, $($tts:tt)*) => {
|
||||
impl Function for $ident {
|
||||
function!(@parse $ident, $($tts)*);
|
||||
}
|
||||
};
|
||||
|
||||
(@parse $ident:ident, parse: plain, $($tts:tt)*) => {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized
|
||||
{
|
||||
Arguments::new(header).done()?;
|
||||
if body.is_some() {
|
||||
err!("expected no body");
|
||||
}
|
||||
Ok($ident)
|
||||
}
|
||||
function!(@layout $($tts)*);
|
||||
};
|
||||
|
||||
(
|
||||
@parse $ident:ident,
|
||||
parse($args:ident, $body:ident, $ctx:ident)
|
||||
$block:block
|
||||
$($tts:tt)*
|
||||
) => {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized
|
||||
{
|
||||
#[allow(unused_mut)] let mut $args = Arguments::new(header);
|
||||
let $body = body;
|
||||
let $ctx = ctx;
|
||||
$block
|
||||
}
|
||||
function!(@layout $($tts)*);
|
||||
};
|
||||
|
||||
(@layout layout($this:pat, $ctx:pat) $block:block) => {
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||
let $ctx = ctx;
|
||||
let $this = self;
|
||||
$block
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Parse the body of a function.
|
||||
/// - If the function does not expect a body, use `forbidden`.
|
||||
/// - If the function can have a body, use `optional`.
|
||||
/// - If the function must have a body, use `required`.
|
||||
#[macro_export]
|
||||
macro_rules! parse {
|
||||
(forbidden: $body:expr) => {
|
||||
if $body.is_some() {
|
||||
err!("unexpected body");
|
||||
}
|
||||
};
|
||||
|
||||
(optional: $body:expr, $ctx:expr) => {
|
||||
if let Some(body) = $body {
|
||||
Some($crate::parsing::parse(body, $ctx)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
(required: $body:expr, $ctx:expr) => {
|
||||
if let Some(body) = $body {
|
||||
$crate::parsing::parse(body, $ctx)?
|
||||
} else {
|
||||
err!("expected body");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a formatted parsing error.
|
||||
#[macro_export]
|
||||
macro_rules! err {
|
||||
($($tts:tt)*) => {
|
||||
return Err(ParseError::new(format!($($tts)*)));
|
||||
};
|
||||
}
|
||||
|
||||
/// Convenient interface for parsing function arguments.
|
||||
pub struct Arguments<'a> {
|
||||
args: Peekable<Iter<'a, Expression>>,
|
||||
}
|
||||
|
||||
impl<'a> Arguments<'a> {
|
||||
pub fn new(header: &'a FuncHeader) -> Arguments<'a> {
|
||||
Arguments {
|
||||
args: header.args.iter().peekable()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_expr(&mut self) -> ParseResult<&'a Expression> {
|
||||
self.args.next()
|
||||
.ok_or_else(|| ParseError::new("expected expression"))
|
||||
}
|
||||
|
||||
pub fn get_ident(&mut self) -> ParseResult<&'a str> {
|
||||
match self.get_expr()? {
|
||||
Expression::Ident(s) => Ok(s.as_str()),
|
||||
_ => Err(ParseError::new("expected identifier")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ident_if_present(&mut self) -> ParseResult<Option<&'a str>> {
|
||||
if self.args.peek().is_some() {
|
||||
self.get_ident().map(|s| Some(s))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn done(&mut self) -> ParseResult<()> {
|
||||
if self.args.peek().is_none() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ParseError::new("unexpected argument"))
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,21 @@ use crate::parsing::{ParseContext, ParseResult};
|
||||
use crate::style::TextStyle;
|
||||
use crate::syntax::{FuncHeader, SyntaxTree};
|
||||
|
||||
#[macro_use]
|
||||
mod helpers;
|
||||
pub use helpers::Arguments;
|
||||
|
||||
/// Useful imports for creating your own functions.
|
||||
pub mod prelude {
|
||||
pub use crate::func::{Command, CommandList, Function};
|
||||
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
||||
pub use crate::layout::{Flow, Alignment, LayoutError, LayoutResult};
|
||||
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
|
||||
pub use crate::size::{Size, Size2D, SizeBox};
|
||||
pub use super::helpers::*;
|
||||
}
|
||||
|
||||
/// Typesetting function types.
|
||||
///
|
||||
/// These types have to be able to parse tokens into themselves and store the
|
@ -1,49 +0,0 @@
|
||||
use super::prelude::*;
|
||||
use crate::layout::Alignment;
|
||||
|
||||
/// Allows to align content in different ways.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AlignFunc {
|
||||
alignment: Alignment,
|
||||
body: Option<SyntaxTree>,
|
||||
}
|
||||
|
||||
impl Function for AlignFunc {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if header.args.len() != 1 || !header.kwargs.is_empty() {
|
||||
return err("align: expected exactly one positional argument");
|
||||
}
|
||||
|
||||
let alignment = if let Expression::Ident(ident) = &header.args[0] {
|
||||
match ident.as_str() {
|
||||
"left" => Alignment::Left,
|
||||
"right" => Alignment::Right,
|
||||
"center" => Alignment::Center,
|
||||
s => return err(format!("invalid alignment specifier: '{}'", s)),
|
||||
}
|
||||
} else {
|
||||
return err(format!(
|
||||
"expected alignment specifier, found: '{}'",
|
||||
header.args[0]
|
||||
));
|
||||
};
|
||||
|
||||
let body = parse_maybe_body(body, ctx)?;
|
||||
|
||||
Ok(AlignFunc { alignment, body })
|
||||
}
|
||||
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||
if let Some(body) = &self.body {
|
||||
let layouts = layout_tree(body, LayoutContext {
|
||||
alignment: self.alignment,
|
||||
.. ctx
|
||||
})?;
|
||||
|
||||
Ok(commands![Command::AddMany(layouts)])
|
||||
} else {
|
||||
Ok(commands![Command::SetAlignment(self.alignment)])
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
use super::prelude::*;
|
||||
use crate::layout::Flow;
|
||||
|
||||
/// Wraps content into a box.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct BoxFunc {
|
||||
body: SyntaxTree,
|
||||
flow: Flow,
|
||||
}
|
||||
|
||||
impl Function for BoxFunc {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
let flow = if header.args.is_empty() {
|
||||
Flow::Vertical
|
||||
} else if header.args.len() == 1 {
|
||||
if let Expression::Ident(ident) = &header.args[0] {
|
||||
match ident.as_str() {
|
||||
"vertical" => Flow::Vertical,
|
||||
"horizontal" => Flow::Horizontal,
|
||||
f => return err(format!("invalid flow specifier: '{}'", f)),
|
||||
}
|
||||
} else {
|
||||
return err(format!(
|
||||
"expected alignment specifier, found: '{}'",
|
||||
header.args[0]
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return err("box: expected flow specifier or no arguments");
|
||||
};
|
||||
|
||||
if let Some(body) = body {
|
||||
Ok(BoxFunc {
|
||||
body: parse(body, ctx)?,
|
||||
flow,
|
||||
})
|
||||
} else {
|
||||
err("box: expected body")
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||
let layout = layout_tree(&self.body, LayoutContext {
|
||||
flow: self.flow,
|
||||
.. ctx
|
||||
})?;
|
||||
|
||||
Ok(commands![Command::AddMany(layout)])
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
use super::prelude::*;
|
||||
|
||||
/// Ends the current line.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LinebreakFunc;
|
||||
|
||||
impl Function for LinebreakFunc {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if has_arguments(header) {
|
||||
return err("linebreak: expected no arguments");
|
||||
}
|
||||
|
||||
if body.is_some() {
|
||||
return err("linebreak: expected no body");
|
||||
}
|
||||
|
||||
Ok(LinebreakFunc)
|
||||
}
|
||||
|
||||
fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
|
||||
Ok(commands![Command::FinishFlexRun])
|
||||
}
|
||||
}
|
||||
|
||||
/// Ends the current page.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PagebreakFunc;
|
||||
|
||||
impl Function for PagebreakFunc {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if has_arguments(header) {
|
||||
return err("pagebreak: expected no arguments");
|
||||
}
|
||||
|
||||
if body.is_some() {
|
||||
return err("pagebreak: expected no body");
|
||||
}
|
||||
|
||||
Ok(PagebreakFunc)
|
||||
}
|
||||
|
||||
fn layout(&self, _: LayoutContext) -> LayoutResult<CommandList> {
|
||||
Ok(commands![Command::FinishLayout])
|
||||
}
|
||||
}
|
@ -2,64 +2,29 @@
|
||||
|
||||
use crate::func::Scope;
|
||||
|
||||
mod align;
|
||||
mod boxed;
|
||||
mod breaks;
|
||||
mod spacing;
|
||||
mod styles;
|
||||
mod structure;
|
||||
mod style;
|
||||
|
||||
/// Useful imports for creating your own functions.
|
||||
pub mod prelude {
|
||||
pub use crate::func::{Command, CommandList, Function};
|
||||
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
||||
pub use crate::layout::{LayoutError, LayoutResult};
|
||||
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
|
||||
pub use super::helpers::*;
|
||||
}
|
||||
|
||||
pub use align::AlignFunc;
|
||||
pub use boxed::BoxFunc;
|
||||
pub use breaks::{LinebreakFunc, PagebreakFunc};
|
||||
pub use spacing::{HorizontalSpaceFunc, VerticalSpaceFunc};
|
||||
pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
|
||||
pub use structure::*;
|
||||
pub use style::*;
|
||||
|
||||
/// Create a scope with all standard functions.
|
||||
pub fn std() -> Scope {
|
||||
let mut std = Scope::new();
|
||||
std.add::<AlignFunc>("align");
|
||||
std.add::<BoxFunc>("box");
|
||||
|
||||
std.add::<LinebreakFunc>("line.break");
|
||||
std.add::<LinebreakFunc>("n");
|
||||
std.add::<PagebreakFunc>("page.break");
|
||||
std.add::<Align>("align");
|
||||
std.add::<Boxed>("box");
|
||||
|
||||
std.add::<HorizontalSpaceFunc>("h");
|
||||
std.add::<VerticalSpaceFunc>("v");
|
||||
std.add::<Linebreak>("line.break");
|
||||
std.add::<Linebreak>("n");
|
||||
std.add::<Pagebreak>("page.break");
|
||||
|
||||
std.add::<HorizontalSpace>("h");
|
||||
std.add::<VerticalSpace>("v");
|
||||
|
||||
std.add::<Bold>("bold");
|
||||
std.add::<Italic>("italic");
|
||||
std.add::<Monospace>("mono");
|
||||
|
||||
std.add::<BoldFunc>("bold");
|
||||
std.add::<ItalicFunc>("italic");
|
||||
std.add::<MonospaceFunc>("mono");
|
||||
std
|
||||
}
|
||||
|
||||
/// Helpers for writing custom functions.
|
||||
pub mod helpers {
|
||||
use super::prelude::*;
|
||||
|
||||
pub fn has_arguments(header: &FuncHeader) -> bool {
|
||||
!header.args.is_empty() || !header.kwargs.is_empty()
|
||||
}
|
||||
|
||||
pub fn parse_maybe_body(body: Option<&str>, ctx: ParseContext) -> ParseResult<Option<SyntaxTree>> {
|
||||
if let Some(body) = body {
|
||||
Ok(Some(parse(body, ctx)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
|
||||
Err(ParseError::new(message))
|
||||
}
|
||||
}
|
||||
|
@ -1,76 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
use super::prelude::*;
|
||||
use crate::size::Size;
|
||||
|
||||
/// Adds vertical space.
|
||||
pub type VerticalSpaceFunc = SpaceFunc<SpaceVertical>;
|
||||
|
||||
/// Adds horizontal space.
|
||||
pub type HorizontalSpaceFunc = SpaceFunc<SpaceHorizontal>;
|
||||
|
||||
/// Adds generic space.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SpaceFunc<F: SpaceFlow> {
|
||||
spacing: Spacing,
|
||||
_phantom: PhantomData<F>,
|
||||
}
|
||||
|
||||
/// Absolute or font-relative spacing.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Spacing {
|
||||
Absolute(Size),
|
||||
Relative(f32),
|
||||
}
|
||||
|
||||
impl<F: SpaceFlow> Function for SpaceFunc<F> {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if header.args.len() != 1 || !header.kwargs.is_empty() {
|
||||
return err("align: expected exactly one positional argument");
|
||||
}
|
||||
|
||||
let spacing = match header.args[0] {
|
||||
Expression::Size(s) => Spacing::Absolute(s),
|
||||
Expression::Number(f) => Spacing::Relative(f as f32),
|
||||
_ => return err("space: expected size or number"),
|
||||
};
|
||||
|
||||
if body.is_some() {
|
||||
return err("space: expected no body");
|
||||
}
|
||||
|
||||
Ok(SpaceFunc {
|
||||
spacing,
|
||||
_phantom: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||
let space = match self.spacing {
|
||||
Spacing::Absolute(s) => s,
|
||||
Spacing::Relative(f) => Size::pt(f * ctx.style.font_size),
|
||||
};
|
||||
|
||||
Ok(commands![F::cmd(space)])
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpaceFlow: std::fmt::Debug + PartialEq + 'static {
|
||||
fn cmd(space: Size) -> Command<'static>;
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SpaceVertical;
|
||||
impl SpaceFlow for SpaceVertical {
|
||||
fn cmd(space: Size) -> Command<'static> {
|
||||
Command::Add(Layout::empty(Size::zero(), space))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SpaceHorizontal;
|
||||
impl SpaceFlow for SpaceHorizontal {
|
||||
fn cmd(space: Size) -> Command<'static> {
|
||||
Command::AddFlex(Layout::empty(space, Size::zero()))
|
||||
}
|
||||
}
|
148
src/library/structure.rs
Normal file
148
src/library/structure.rs
Normal file
@ -0,0 +1,148 @@
|
||||
use crate::func::prelude::*;
|
||||
|
||||
/// Ends the current page.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Pagebreak;
|
||||
|
||||
function! {
|
||||
data: Pagebreak,
|
||||
parse: plain,
|
||||
|
||||
layout(_, _) {
|
||||
Ok(commands![Command::FinishLayout])
|
||||
}
|
||||
}
|
||||
|
||||
/// Ends the current line.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Linebreak;
|
||||
|
||||
function! {
|
||||
data: Linebreak,
|
||||
parse: plain,
|
||||
|
||||
layout(_, _) {
|
||||
Ok(commands![Command::FinishFlexRun])
|
||||
}
|
||||
}
|
||||
|
||||
/// Aligns content in different ways.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Align {
|
||||
body: Option<SyntaxTree>,
|
||||
alignment: Alignment,
|
||||
}
|
||||
|
||||
function! {
|
||||
data: Align,
|
||||
|
||||
parse(args, body, ctx) {
|
||||
let body = parse!(optional: body, ctx);
|
||||
let alignment = match args.get_ident()? {
|
||||
"left" => Alignment::Left,
|
||||
"right" => Alignment::Right,
|
||||
"center" => Alignment::Center,
|
||||
s => err!("invalid alignment specifier: {}", s),
|
||||
};
|
||||
args.done()?;
|
||||
|
||||
Ok(Align {
|
||||
body,
|
||||
alignment,
|
||||
})
|
||||
}
|
||||
|
||||
layout(this, ctx) {
|
||||
Ok(commands![match &this.body {
|
||||
Some(body) => {
|
||||
Command::AddMany(layout_tree(body, LayoutContext {
|
||||
alignment: this.alignment,
|
||||
.. ctx
|
||||
})?)
|
||||
}
|
||||
None => Command::SetAlignment(this.alignment)
|
||||
}])
|
||||
}
|
||||
}
|
||||
|
||||
/// Layouts content into a box.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Boxed {
|
||||
body: SyntaxTree,
|
||||
flow: Flow,
|
||||
}
|
||||
|
||||
function! {
|
||||
data: Boxed,
|
||||
|
||||
parse(args, body, ctx) {
|
||||
let body = parse!(required: body, ctx);
|
||||
|
||||
let mut flow = Flow::Vertical;
|
||||
if let Some(ident) = args.get_ident_if_present()? {
|
||||
flow = match ident {
|
||||
"vertical" => Flow::Vertical,
|
||||
"horizontal" => Flow::Horizontal,
|
||||
f => err!("invalid flow specifier: {}", f),
|
||||
};
|
||||
}
|
||||
args.done()?;
|
||||
|
||||
Ok(Boxed {
|
||||
body,
|
||||
flow,
|
||||
})
|
||||
}
|
||||
|
||||
layout(this, ctx) {
|
||||
Ok(commands![
|
||||
Command::AddMany(layout_tree(&this.body, LayoutContext {
|
||||
flow: this.flow,
|
||||
.. ctx
|
||||
})?)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! spacefunc {
|
||||
($ident:ident, $name:expr, $var:ident => $command:expr) => {
|
||||
/// Adds whitespace.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct $ident(Spacing);
|
||||
|
||||
function! {
|
||||
data: $ident,
|
||||
|
||||
parse(args, body, _ctx) {
|
||||
parse!(forbidden: body);
|
||||
|
||||
let spacing = match args.get_expr()? {
|
||||
Expression::Size(s) => Spacing::Absolute(*s),
|
||||
Expression::Number(f) => Spacing::Relative(*f as f32),
|
||||
_ => err!("invalid spacing, expected size or number"),
|
||||
};
|
||||
|
||||
Ok($ident(spacing))
|
||||
}
|
||||
|
||||
layout(this, ctx) {
|
||||
let $var = match this.0 {
|
||||
Spacing::Absolute(s) => s,
|
||||
Spacing::Relative(f) => Size::pt(f * ctx.style.font_size),
|
||||
};
|
||||
|
||||
Ok(commands![$command])
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Absolute or font-relative spacing.
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Spacing {
|
||||
Absolute(Size),
|
||||
Relative(f32),
|
||||
}
|
||||
|
||||
spacefunc!(HorizontalSpace, "h", space => Command::AddFlex(Layout::empty(space, Size::zero())));
|
||||
spacefunc!(VerticalSpace, "v", space => Command::Add(Layout::empty(Size::zero(), space)));
|
39
src/library/style.rs
Normal file
39
src/library/style.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use crate::func::prelude::*;
|
||||
use toddle::query::FontClass;
|
||||
|
||||
macro_rules! stylefunc {
|
||||
($ident:ident) => {
|
||||
/// Styles text.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct $ident {
|
||||
body: Option<SyntaxTree>
|
||||
}
|
||||
|
||||
function! {
|
||||
data: $ident,
|
||||
|
||||
parse(args, body, ctx) {
|
||||
args.done()?;
|
||||
Ok($ident { body: parse!(optional: body, ctx) })
|
||||
}
|
||||
|
||||
layout(this, ctx) {
|
||||
let mut new_style = ctx.style.clone();
|
||||
new_style.toggle_class(FontClass::$ident);
|
||||
|
||||
Ok(match &this.body {
|
||||
Some(body) => commands![
|
||||
Command::SetStyle(new_style),
|
||||
Command::Layout(body),
|
||||
Command::SetStyle(ctx.style.clone()),
|
||||
],
|
||||
None => commands![Command::SetStyle(new_style)]
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
stylefunc!(Italic);
|
||||
stylefunc!(Bold);
|
||||
stylefunc!(Monospace);
|
@ -1,65 +0,0 @@
|
||||
use toddle::query::FontClass;
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
macro_rules! style_func {
|
||||
(
|
||||
$(#[$outer:meta])*
|
||||
pub struct $struct:ident { $name:expr },
|
||||
$style:ident => $class:ident
|
||||
) => {
|
||||
$(#[$outer])*
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct $struct {
|
||||
body: Option<SyntaxTree>
|
||||
}
|
||||
|
||||
impl Function for $struct {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized {
|
||||
// Accept only invocations without arguments and with body.
|
||||
if has_arguments(header) {
|
||||
return err(format!("{}: expected no arguments", $name));
|
||||
}
|
||||
|
||||
let body = parse_maybe_body(body, ctx)?;
|
||||
|
||||
Ok($struct { body })
|
||||
}
|
||||
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<CommandList> {
|
||||
let mut new_style = ctx.style.clone();
|
||||
new_style.toggle_class(FontClass::$class);
|
||||
|
||||
if let Some(body) = &self.body {
|
||||
let saved_style = ctx.style.clone();
|
||||
Ok(commands![
|
||||
Command::SetStyle(new_style),
|
||||
Command::Layout(body),
|
||||
Command::SetStyle(saved_style),
|
||||
])
|
||||
} else {
|
||||
Ok(commands![Command::SetStyle(new_style)])
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
style_func! {
|
||||
/// Typesets text in bold.
|
||||
pub struct BoldFunc { "bold" },
|
||||
style => Bold
|
||||
}
|
||||
|
||||
style_func! {
|
||||
/// Typesets text in italics.
|
||||
pub struct ItalicFunc { "italic" },
|
||||
style => Italic
|
||||
}
|
||||
|
||||
style_func! {
|
||||
/// Typesets text in monospace.
|
||||
pub struct MonospaceFunc { "mono" },
|
||||
style => Monospace
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user