mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Add standard align
function and support right-alignment ➡️
This commit is contained in:
parent
61470fba68
commit
8f788f9a4f
72
src/func.rs
72
src/func.rs
@ -3,12 +3,10 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use toddle::query::FontClass;
|
|
||||||
|
|
||||||
use crate::layout::{layout, Layout, LayoutContext, LayoutResult};
|
use crate::layout::{Layout, LayoutContext, LayoutResult};
|
||||||
use crate::layout::flex::FlexLayout;
|
use crate::parsing::{ParseContext, ParseResult};
|
||||||
use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
use crate::syntax::FuncHeader;
|
||||||
use crate::syntax::{SyntaxTree, FuncHeader};
|
|
||||||
|
|
||||||
|
|
||||||
/// Typesetting function types.
|
/// Typesetting function types.
|
||||||
@ -79,11 +77,7 @@ impl Scope {
|
|||||||
|
|
||||||
/// Create a new scope with the standard functions contained.
|
/// Create a new scope with the standard functions contained.
|
||||||
pub fn with_std() -> Scope {
|
pub fn with_std() -> Scope {
|
||||||
let mut std = Scope::new();
|
crate::library::std()
|
||||||
std.add::<BoldFunc>("bold");
|
|
||||||
std.add::<ItalicFunc>("italic");
|
|
||||||
std.add::<MonospaceFunc>("mono");
|
|
||||||
std
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a function type to the scope giving it a name.
|
/// Add a function type to the scope giving it a name.
|
||||||
@ -108,61 +102,3 @@ impl Debug for Scope {
|
|||||||
write!(f, "{:?}", self.parsers.keys())
|
write!(f, "{:?}", self.parsers.keys())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates style functions like bold and italic.
|
|
||||||
macro_rules! style_func {
|
|
||||||
($(#[$outer:meta])* pub struct $struct:ident { $name:expr },
|
|
||||||
$style:ident => $style_change:block) => {
|
|
||||||
$(#[$outer])*
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct $struct { body: 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 header.args.is_empty() && header.kwargs.is_empty() {
|
|
||||||
if let Some(body) = body {
|
|
||||||
Ok($struct { body: parse(body, ctx)? })
|
|
||||||
} else {
|
|
||||||
Err(ParseError::new(format!("expected body for function `{}`", $name)))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(ParseError::new(format!("unexpected arguments to function `{}`", $name)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
|
|
||||||
// Change the context.
|
|
||||||
let mut $style = ctx.style.clone();
|
|
||||||
$style_change
|
|
||||||
|
|
||||||
// Create a box and put it into a flex layout.
|
|
||||||
let boxed = layout(&self.body, LayoutContext {
|
|
||||||
style: &$style,
|
|
||||||
.. ctx
|
|
||||||
})?;
|
|
||||||
let flex = FlexLayout::from_box(boxed);
|
|
||||||
|
|
||||||
Ok(Some(Layout::Flex(flex)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
style_func! {
|
|
||||||
/// Typesets text in bold.
|
|
||||||
pub struct BoldFunc { "bold" },
|
|
||||||
style => { style.toggle_class(FontClass::Bold) }
|
|
||||||
}
|
|
||||||
|
|
||||||
style_func! {
|
|
||||||
/// Typesets text in italics.
|
|
||||||
pub struct ItalicFunc { "italic" },
|
|
||||||
style => { style.toggle_class(FontClass::Italic) }
|
|
||||||
}
|
|
||||||
|
|
||||||
style_func! {
|
|
||||||
/// Typesets text in monospace.
|
|
||||||
pub struct MonospaceFunc { "mono" },
|
|
||||||
style => { style.toggle_class(FontClass::Monospace) }
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use crate::doc::{Document, Page, LayoutAction};
|
use crate::doc::{Document, Page, LayoutAction};
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
use super::{ActionList, LayoutSpace, LayoutResult, LayoutError};
|
use super::{ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
|
||||||
|
|
||||||
|
|
||||||
/// A box layout has a fixed width and height and composes of actions.
|
/// A box layout has a fixed width and height and composes of actions.
|
||||||
@ -37,7 +37,7 @@ pub struct BoxContext {
|
|||||||
/// Layouts boxes block-style.
|
/// Layouts boxes block-style.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BoxLayouter {
|
pub struct BoxLayouter {
|
||||||
ctx: BoxContext,
|
pub ctx: BoxContext,
|
||||||
actions: ActionList,
|
actions: ActionList,
|
||||||
dimensions: Size2D,
|
dimensions: Size2D,
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
@ -51,9 +51,15 @@ impl BoxLayouter {
|
|||||||
BoxLayouter {
|
BoxLayouter {
|
||||||
ctx,
|
ctx,
|
||||||
actions: ActionList::new(),
|
actions: ActionList::new(),
|
||||||
dimensions: Size2D::zero(),
|
dimensions: match ctx.space.alignment {
|
||||||
|
Alignment::Left => Size2D::zero(),
|
||||||
|
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||||
|
},
|
||||||
usable: space.usable(),
|
usable: space.usable(),
|
||||||
cursor: Size2D::new(space.padding.left, space.padding.right),
|
cursor: Size2D::new(match ctx.space.alignment {
|
||||||
|
Alignment::Left => space.padding.left,
|
||||||
|
Alignment::Right => space.dimensions.x - space.padding.right,
|
||||||
|
}, space.padding.top),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,12 +77,18 @@ impl BoxLayouter {
|
|||||||
return Err(LayoutError::NotEnoughSpace);
|
return Err(LayoutError::NotEnoughSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the dimensions as they fit.
|
// Apply the dimensions if they fit.
|
||||||
let height = layout.dimensions.y;
|
|
||||||
self.dimensions = new;
|
self.dimensions = new;
|
||||||
|
let width = layout.dimensions.x;
|
||||||
|
let height = layout.dimensions.y;
|
||||||
|
|
||||||
|
let position = match self.ctx.space.alignment {
|
||||||
|
Alignment::Left => self.cursor,
|
||||||
|
Alignment::Right => self.cursor - Size2D::with_x(width),
|
||||||
|
};
|
||||||
|
|
||||||
// Add the box.
|
// Add the box.
|
||||||
self.add_box_absolute(self.cursor, layout);
|
self.add_box_absolute(position, layout);
|
||||||
|
|
||||||
// Adjust the cursor.
|
// Adjust the cursor.
|
||||||
self.cursor.y += height;
|
self.cursor.y += height;
|
||||||
@ -86,11 +98,7 @@ impl BoxLayouter {
|
|||||||
|
|
||||||
/// Add a sublayout at an absolute position.
|
/// Add a sublayout at an absolute position.
|
||||||
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
||||||
// Move all actions into this layout and translate absolute positions.
|
self.actions.add_box_absolute(position, layout);
|
||||||
self.actions.reset_origin();
|
|
||||||
self.actions.add(LayoutAction::MoveAbsolute(position));
|
|
||||||
self.actions.set_origin(position);
|
|
||||||
self.actions.extend(layout.actions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add some space in between two boxes.
|
/// Add some space in between two boxes.
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
//! Flexible and lazy layouting of boxes.
|
//! Flexible and lazy layouting of boxes.
|
||||||
|
|
||||||
use crate::doc::LayoutAction;
|
|
||||||
use crate::size::{Size, Size2D};
|
use crate::size::{Size, Size2D};
|
||||||
use super::{BoxLayout, ActionList, LayoutSpace, LayoutResult, LayoutError};
|
use super::{BoxLayout, ActionList, LayoutSpace, Alignment, LayoutResult, LayoutError};
|
||||||
|
|
||||||
|
|
||||||
/// A flex layout consists of a yet unarranged list of boxes.
|
/// A flex layout consists of a yet unarranged list of boxes.
|
||||||
@ -81,7 +80,9 @@ struct FlexFinisher {
|
|||||||
dimensions: Size2D,
|
dimensions: Size2D,
|
||||||
usable: Size2D,
|
usable: Size2D,
|
||||||
cursor: Size2D,
|
cursor: Size2D,
|
||||||
line: Size2D,
|
line_metrics: Size2D,
|
||||||
|
line_content: Vec<(Size2D, BoxLayout)>,
|
||||||
|
glue: Option<BoxLayout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlexFinisher {
|
impl FlexFinisher {
|
||||||
@ -92,10 +93,15 @@ impl FlexFinisher {
|
|||||||
units: layout.units,
|
units: layout.units,
|
||||||
ctx,
|
ctx,
|
||||||
actions: ActionList::new(),
|
actions: ActionList::new(),
|
||||||
dimensions: Size2D::zero(),
|
dimensions: match ctx.space.alignment {
|
||||||
|
Alignment::Left => Size2D::zero(),
|
||||||
|
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||||
|
},
|
||||||
usable: space.usable(),
|
usable: space.usable(),
|
||||||
cursor: Size2D::new(space.padding.left, space.padding.top),
|
cursor: Size2D::new(space.padding.left, space.padding.top),
|
||||||
line: Size2D::zero(),
|
line_metrics: Size2D::zero(),
|
||||||
|
line_content: vec![],
|
||||||
|
glue: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,14 +134,20 @@ impl FlexFinisher {
|
|||||||
|
|
||||||
/// Layout the box.
|
/// Layout the box.
|
||||||
fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
|
fn boxed(&mut self, boxed: BoxLayout) -> LayoutResult<()> {
|
||||||
|
let last_glue_x = self.glue.as_ref()
|
||||||
|
.map(|g| g.dimensions.x)
|
||||||
|
.unwrap_or(Size::zero());
|
||||||
|
|
||||||
// Move to the next line if necessary.
|
// Move to the next line if necessary.
|
||||||
if self.line.x + boxed.dimensions.x > self.usable.x {
|
if self.line_metrics.x + boxed.dimensions.x + last_glue_x > self.usable.x {
|
||||||
// If it still does not fit, we stand no chance.
|
// If it still does not fit, we stand no chance.
|
||||||
if boxed.dimensions.x > self.usable.x {
|
if boxed.dimensions.x > self.usable.x {
|
||||||
return Err(LayoutError::NotEnoughSpace);
|
return Err(LayoutError::NotEnoughSpace);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.newline();
|
self.newline();
|
||||||
|
} else if let Some(glue) = self.glue.take() {
|
||||||
|
self.append(glue);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.append(boxed);
|
self.append(boxed);
|
||||||
@ -145,37 +157,51 @@ impl FlexFinisher {
|
|||||||
|
|
||||||
/// Layout the glue.
|
/// Layout the glue.
|
||||||
fn glue(&mut self, glue: BoxLayout) {
|
fn glue(&mut self, glue: BoxLayout) {
|
||||||
// Only add the glue if it fits on the line, otherwise move to the next line.
|
self.glue = Some(glue);
|
||||||
if self.line.x + glue.dimensions.x > self.usable.x {
|
|
||||||
self.newline();
|
|
||||||
} else {
|
|
||||||
self.append(glue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a box to the layout without checking anything.
|
/// Append a box to the layout without checking anything.
|
||||||
fn append(&mut self, layout: BoxLayout) {
|
fn append(&mut self, layout: BoxLayout) {
|
||||||
// Move all actions into this layout and translate absolute positions.
|
let dim = layout.dimensions;
|
||||||
self.actions.reset_origin();
|
self.line_content.push((self.cursor, layout));
|
||||||
self.actions.add(LayoutAction::MoveAbsolute(self.cursor));
|
|
||||||
self.actions.set_origin(self.cursor);
|
|
||||||
self.actions.extend(layout.actions);
|
|
||||||
|
|
||||||
// Adjust the sizes.
|
self.line_metrics.x += dim.x;
|
||||||
self.line.x += layout.dimensions.x;
|
self.line_metrics.y = crate::size::max(self.line_metrics.y, dim.y);
|
||||||
self.line.y = crate::size::max(self.line.y, layout.dimensions.y);
|
self.cursor.x += dim.x;
|
||||||
self.cursor.x += layout.dimensions.x;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move to the next line.
|
/// Move to the next line.
|
||||||
fn newline(&mut self) {
|
fn newline(&mut self) {
|
||||||
self.dimensions.x = crate::size::max(self.dimensions.x, self.line.x);
|
// Move all actions into this layout and translate absolute positions.
|
||||||
|
let remaining_space = Size2D::with_x(self.ctx.space.usable().x - self.line_metrics.x);
|
||||||
|
for (cursor, layout) in self.line_content.drain(..) {
|
||||||
|
let position = match self.ctx.space.alignment {
|
||||||
|
Alignment::Left => cursor,
|
||||||
|
Alignment::Right => {
|
||||||
|
// Right align everything by shifting it right by the
|
||||||
|
// amount of space left to the right of the line.
|
||||||
|
cursor + remaining_space
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.actions.add_box_absolute(position, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stretch the dimensions to at least the line width.
|
||||||
|
self.dimensions.x = crate::size::max(self.dimensions.x, self.line_metrics.x);
|
||||||
|
|
||||||
|
// If we wrote a line previously add the inter-line spacing.
|
||||||
if self.dimensions.y > Size::zero() {
|
if self.dimensions.y > Size::zero() {
|
||||||
self.dimensions.y += self.ctx.flex_spacing;
|
self.dimensions.y += self.ctx.flex_spacing;
|
||||||
}
|
}
|
||||||
self.dimensions.y += self.line.y;
|
|
||||||
|
self.dimensions.y += self.line_metrics.y;
|
||||||
|
|
||||||
|
// Reset the cursor the left and move down by the line and the inter-line spacing.
|
||||||
self.cursor.x = self.ctx.space.padding.left;
|
self.cursor.x = self.ctx.space.padding.left;
|
||||||
self.cursor.y += self.line.y + self.ctx.flex_spacing;
|
self.cursor.y += self.line_metrics.y + self.ctx.flex_spacing;
|
||||||
self.line = Size2D::zero();
|
|
||||||
|
// Reset the current line metrics.
|
||||||
|
self.line_metrics = Size2D::zero();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,11 +52,20 @@ pub struct LayoutSpace {
|
|||||||
pub dimensions: Size2D,
|
pub dimensions: Size2D,
|
||||||
/// Padding that should be respected on each side.
|
/// Padding that should be respected on each side.
|
||||||
pub padding: SizeBox,
|
pub padding: SizeBox,
|
||||||
|
/// The alignment to use for the content.
|
||||||
|
pub alignment: Alignment,
|
||||||
/// Whether to shrink the dimensions to fit the content or the keep the
|
/// Whether to shrink the dimensions to fit the content or the keep the
|
||||||
/// original ones.
|
/// original ones.
|
||||||
pub shrink_to_fit: bool,
|
pub shrink_to_fit: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where to align content.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Alignment {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
impl LayoutSpace {
|
impl LayoutSpace {
|
||||||
/// The actually usable area.
|
/// The actually usable area.
|
||||||
pub fn usable(&self) -> Size2D {
|
pub fn usable(&self) -> Size2D {
|
||||||
@ -157,6 +166,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
space: LayoutSpace {
|
space: LayoutSpace {
|
||||||
dimensions: self.box_layouter.remaining(),
|
dimensions: self.box_layouter.remaining(),
|
||||||
padding: SizeBox::zero(),
|
padding: SizeBox::zero(),
|
||||||
|
alignment: self.box_layouter.ctx.space.alignment,
|
||||||
shrink_to_fit: true,
|
shrink_to_fit: true,
|
||||||
},
|
},
|
||||||
flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
|
flex_spacing: (self.style.line_spacing - 1.0) * Size::pt(self.style.font_size),
|
||||||
@ -173,6 +183,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
space: LayoutSpace {
|
space: LayoutSpace {
|
||||||
dimensions: self.box_layouter.remaining(),
|
dimensions: self.box_layouter.remaining(),
|
||||||
padding: SizeBox::zero(),
|
padding: SizeBox::zero(),
|
||||||
|
alignment: self.box_layouter.ctx.space.alignment,
|
||||||
shrink_to_fit: true,
|
shrink_to_fit: true,
|
||||||
},
|
},
|
||||||
})?;
|
})?;
|
||||||
@ -196,8 +207,8 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
|||||||
/// Manipulates and optimizes a list of actions.
|
/// Manipulates and optimizes a list of actions.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ActionList {
|
pub struct ActionList {
|
||||||
|
pub origin: Size2D,
|
||||||
actions: Vec<LayoutAction>,
|
actions: Vec<LayoutAction>,
|
||||||
origin: Size2D,
|
|
||||||
active_font: (usize, f32),
|
active_font: (usize, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,15 +243,12 @@ impl ActionList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move the origin for the upcomming actions. Absolute moves will be
|
/// Add all actions from a box layout at a position. A move to the position
|
||||||
/// changed by that origin.
|
/// is generated and all moves inside the box layout are translated as necessary.
|
||||||
pub fn set_origin(&mut self, origin: Size2D) {
|
pub fn add_box_absolute(&mut self, position: Size2D, layout: BoxLayout) {
|
||||||
self.origin = origin;
|
self.actions.push(LayoutAction::MoveAbsolute(position));
|
||||||
}
|
self.origin = position;
|
||||||
|
self.extend(layout.actions);
|
||||||
/// Reset the origin to zero.
|
|
||||||
pub fn reset_origin(&mut self) {
|
|
||||||
self.origin = Size2D::zero();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether there are any actions in this list.
|
/// Whether there are any actions in this list.
|
||||||
|
@ -20,7 +20,7 @@ use toddle::query::{FontLoader, SharedFontLoader, FontProvider};
|
|||||||
use crate::doc::Document;
|
use crate::doc::Document;
|
||||||
use crate::func::Scope;
|
use crate::func::Scope;
|
||||||
use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||||
use crate::layout::{layout, LayoutContext, LayoutSpace, LayoutError, LayoutResult};
|
use crate::layout::{layout, LayoutContext, Alignment, LayoutSpace, LayoutError, LayoutResult};
|
||||||
use crate::layout::boxed::BoxLayout;
|
use crate::layout::boxed::BoxLayout;
|
||||||
use crate::style::{PageStyle, TextStyle};
|
use crate::style::{PageStyle, TextStyle};
|
||||||
use crate::syntax::SyntaxTree;
|
use crate::syntax::SyntaxTree;
|
||||||
@ -35,6 +35,7 @@ pub mod parsing;
|
|||||||
pub mod size;
|
pub mod size;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
pub mod library;
|
||||||
|
|
||||||
|
|
||||||
/// Transforms source code into typesetted documents.
|
/// Transforms source code into typesetted documents.
|
||||||
@ -92,6 +93,7 @@ impl<'p> Typesetter<'p> {
|
|||||||
space: LayoutSpace {
|
space: LayoutSpace {
|
||||||
dimensions: self.page_style.dimensions,
|
dimensions: self.page_style.dimensions,
|
||||||
padding: self.page_style.margins,
|
padding: self.page_style.margins,
|
||||||
|
alignment: Alignment::Left,
|
||||||
shrink_to_fit: false,
|
shrink_to_fit: false,
|
||||||
},
|
},
|
||||||
})?;
|
})?;
|
||||||
@ -184,5 +186,6 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn shakespeare() {
|
fn shakespeare() {
|
||||||
test("shakespeare", include_str!("../test/shakespeare.tps"));
|
test("shakespeare", include_str!("../test/shakespeare.tps"));
|
||||||
|
test("shakespeare-right", &format!("[align:right][{}]", include_str!("../test/shakespeare.tps")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
51
src/library/align.rs
Normal file
51
src/library/align.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//! Alignment function.
|
||||||
|
|
||||||
|
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("expected exactly one positional argument specifying the alignment");
|
||||||
|
}
|
||||||
|
|
||||||
|
let alignment = if let Expression::Ident(ident) = &header.args[0] {
|
||||||
|
match ident.as_str() {
|
||||||
|
"left" => Alignment::Left,
|
||||||
|
"right" => Alignment::Right,
|
||||||
|
s => return err(format!("invalid alignment specifier: '{}'", s)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return err(format!("expected alignment specifier, found: '{}'", header.args[0]));
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = if let Some(body) = body {
|
||||||
|
Some(parse(body, ctx)?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(AlignFunc { alignment, body })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self, mut ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
|
||||||
|
if let Some(body) = &self.body {
|
||||||
|
// Override the previous alignment and do the layouting.
|
||||||
|
ctx.space.alignment = self.alignment;
|
||||||
|
layout(body, ctx)
|
||||||
|
.map(|l| Some(Layout::Boxed(l)))
|
||||||
|
} else {
|
||||||
|
unimplemented!("context-modifying align func")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/library/mod.rs
Normal file
34
src/library/mod.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
//! The standard library for the _Typst_ language.
|
||||||
|
|
||||||
|
use crate::func::Scope;
|
||||||
|
|
||||||
|
mod align;
|
||||||
|
mod styles;
|
||||||
|
|
||||||
|
/// Useful imports for creating your own functions.
|
||||||
|
pub mod prelude {
|
||||||
|
pub use crate::syntax::{SyntaxTree, FuncHeader, Expression};
|
||||||
|
pub use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||||
|
pub use crate::layout::{layout, Layout, LayoutContext, LayoutResult, LayoutError};
|
||||||
|
pub use crate::layout::flex::FlexLayout;
|
||||||
|
pub use crate::layout::boxed::BoxLayout;
|
||||||
|
pub use crate::func::Function;
|
||||||
|
|
||||||
|
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
|
||||||
|
Err(ParseError::new(message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use align::AlignFunc;
|
||||||
|
pub use styles::{ItalicFunc, BoldFunc, MonospaceFunc};
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a scope with all standard functions.
|
||||||
|
pub fn std() -> Scope {
|
||||||
|
let mut std = Scope::new();
|
||||||
|
std.add::<BoldFunc>("bold");
|
||||||
|
std.add::<ItalicFunc>("italic");
|
||||||
|
std.add::<MonospaceFunc>("mono");
|
||||||
|
std.add::<AlignFunc>("align");
|
||||||
|
std
|
||||||
|
}
|
64
src/library/styles.rs
Normal file
64
src/library/styles.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
//! Basic style functions: bold, italic, monospace.
|
||||||
|
|
||||||
|
use super::prelude::*;
|
||||||
|
use toddle::query::FontClass;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
macro_rules! style_func {
|
||||||
|
($(#[$outer:meta])* pub struct $struct:ident { $name:expr },
|
||||||
|
$style:ident => $style_change:block) => {
|
||||||
|
$(#[$outer])*
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct $struct { body: 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 header.args.is_empty() && header.kwargs.is_empty() {
|
||||||
|
if let Some(body) = body {
|
||||||
|
Ok($struct { body: parse(body, ctx)? })
|
||||||
|
} else {
|
||||||
|
Err(ParseError::new(format!("expected body for function `{}`", $name)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(ParseError::new(format!("unexpected arguments to function `{}`", $name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout(&self, ctx: LayoutContext) -> LayoutResult<Option<Layout>> {
|
||||||
|
// Change the context.
|
||||||
|
let mut $style = ctx.style.clone();
|
||||||
|
$style_change
|
||||||
|
|
||||||
|
// Create a box and put it into a flex layout.
|
||||||
|
let boxed = layout(&self.body, LayoutContext {
|
||||||
|
style: &$style,
|
||||||
|
.. ctx
|
||||||
|
})?;
|
||||||
|
let flex = FlexLayout::from_box(boxed);
|
||||||
|
|
||||||
|
Ok(Some(Layout::Flex(flex)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
style_func! {
|
||||||
|
/// Typesets text in bold.
|
||||||
|
pub struct BoldFunc { "bold" },
|
||||||
|
style => { style.toggle_class(FontClass::Bold) }
|
||||||
|
}
|
||||||
|
|
||||||
|
style_func! {
|
||||||
|
/// Typesets text in italics.
|
||||||
|
pub struct ItalicFunc { "italic" },
|
||||||
|
style => { style.toggle_class(FontClass::Italic) }
|
||||||
|
}
|
||||||
|
|
||||||
|
style_func! {
|
||||||
|
/// Typesets text in monospace.
|
||||||
|
pub struct MonospaceFunc { "mono" },
|
||||||
|
style => { style.toggle_class(FontClass::Monospace) }
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user