diff --git a/src/error.rs b/src/error.rs index cd1077410..b08d27dd9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,22 +1,36 @@ +//! Errors in source code. +//! +//! There are no fatal errors in _Typst_. The document will always compile and +//! yield a layout. However, this is a best effort process and bad things will +//! still generate errors and warnings. + use serde::Serialize; use crate::syntax::span::SpanVec; +/// A spanned list of errors. pub type Errors = SpanVec; +/// An error that arose in parsing or layouting. #[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct Error { + /// An error message describing the problem. pub message: String, + /// How severe / important the error is. pub severity: Severity, } +/// How severe / important an error is. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize)] pub enum Severity { + /// Something in the code is not good. Warning, + /// Something in the code is wrong! Error, } impl Error { + /// Create a new error from message and severity. pub fn new(message: impl Into, severity: Severity) -> Error { Error { message: message.into(), severity } } diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 421ffb199..48aa2510c 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -32,20 +32,21 @@ impl PdfExporter { PdfExporter {} } - /// Export a finished multi-layout. The layout needs to have been created with the same - /// font loader passed in here since the indices must match. The PDF data is written into - /// the target writable and the number of bytes written is returned. + /// Export a layouted list of boxes. The same font loader as used for + /// layouting needs to be passed in here since the layout only contains + /// indices referencing the loaded fonts. The raw PDF ist written into the + /// target writable, returning the number of bytes written. pub fn export( &self, layout: &MultiLayout, loader: &SharedFontLoader, target: W, - ) -> PdfResult - { + ) -> PdfResult { ExportProcess::new(layout, loader, target)?.write() } } +/// The data relevant to the export of one document. struct ExportProcess<'d, W: Write> { writer: PdfWriter, layouts: &'d MultiLayout, @@ -66,7 +67,7 @@ struct ExportProcess<'d, W: Write> { fonts: Vec, } -/// Indicates which range of PDF IDs are used for which contents. +/// Indicates which range of PDF IDs will be used for which contents. struct Offsets { catalog: Ref, page_tree: Ref, @@ -76,12 +77,13 @@ struct Offsets { } impl<'d, W: Write> ExportProcess<'d, W> { + /// Prepare the export. Only once [`ExportProcess::write`] is called the + /// writing really happens. fn new( layouts: &'d MultiLayout, font_loader: &SharedFontLoader, target: W, - ) -> PdfResult> - { + ) -> PdfResult> { let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?; let offsets = Self::calculate_offsets(layouts.len(), fonts.len()); @@ -94,22 +96,22 @@ impl<'d, W: Write> ExportProcess<'d, W> { }) } - /// Subsets all fonts and assings each one a new index. The returned hash map - /// maps the old indices (used by the layouts) to the new one used in the PDF. - /// The new ones index into the returned vector. + /// Subsets all fonts and assign a new PDF-internal index to each one. The + /// returned hash map maps the old indices (used by the layouts) to the new + /// one used in the PDF. The new ones index into the returned vector of + /// owned fonts. fn subset_fonts( layouts: &'d MultiLayout, font_loader: &SharedFontLoader - ) -> PdfResult<(Vec, HashMap)> - { + ) -> PdfResult<(Vec, HashMap)> { let mut fonts = Vec::new(); let mut font_chars: HashMap> = HashMap::new(); let mut old_to_new: HashMap = HashMap::new(); let mut new_to_old: HashMap = HashMap::new(); let mut active_font = FontIndex::MAX; - // We want to find out which fonts are used at all and which are chars - // are used for these. We use this information to create subsetted fonts. + // We want to find out which fonts are used at all and which chars are + // used for those. We use this information to create subsetted fonts. for layout in layouts { for action in &layout.actions { match action { @@ -141,11 +143,13 @@ impl<'d, W: Write> ExportProcess<'d, W> { let num_fonts = old_to_new.len(); let mut font_loader = font_loader.borrow_mut(); + // All tables not listed here are dropped. const SUBSET_TABLES: [&str; 13] = [ "name", "OS/2", "post", "head", "hhea", "hmtx", "maxp", "cmap", "cvt ", "fpgm", "prep", "loca", "glyf", ]; + // Do the subsetting. for index in 0 .. num_fonts { let old_index = new_to_old[&index]; let font = font_loader.get_with_index(old_index); @@ -158,8 +162,9 @@ impl<'d, W: Write> ExportProcess<'d, W> { Ok((fonts, old_to_new)) } - /// We need to know in advance which IDs to use for which objects to cross-reference them. - /// Therefore, we calculate them in the beginning. + /// We need to know in advance which IDs to use for which objects to + /// cross-reference them. Therefore, we calculate the indices in the + /// beginning. fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets { let catalog = 1; let page_tree = catalog + 1; @@ -176,7 +181,7 @@ impl<'d, W: Write> ExportProcess<'d, W> { } } - /// Write everything (entry point). + /// Write everything (writing entry point). fn write(&mut self) -> PdfResult { self.writer.write_header(Version::new(1, 7))?; self.write_preface()?; @@ -241,6 +246,8 @@ impl<'d, W: Write> ExportProcess<'d, W> { /// Write the content of a page. fn write_page(&mut self, id: u32, page: &Layout) -> PdfResult<()> { + // Moves and font switches are always cached and only flushed once + // needed. let mut text = Text::new(); let mut active_font = (std::usize::MAX, 0.0); let mut next_pos = None; @@ -280,6 +287,8 @@ impl<'d, W: Write> ExportProcess<'d, W> { let mut id = self.offsets.fonts.0; for font in &mut self.fonts { + // --------------------------------------------- + // Extract information from the name table. let name = font .read_table::()? .get_decoded(NameEntry::PostScriptName) @@ -300,7 +309,7 @@ impl<'d, W: Write> ExportProcess<'d, W> { )?; // --------------------------------------------- - // Extract information from the head table. + // Extract information from the head and hmtx tables. let head = font.read_table::
()?; let font_unit_ratio = 1.0 / (head.units_per_em as f32); @@ -356,29 +365,33 @@ impl<'d, W: Write> ExportProcess<'d, W> { let os2 = font.read_table::()?; // Write the font descriptor (contains the global information about the font). - self.writer.write_obj( - id + 2, - FontDescriptor::new(base_font, flags, italic_angle) - .font_bbox(bounding_box) - .ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32)) - .descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32)) - .cap_height(font_unit_to_glyph_unit( - os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32, - )) - .stem_v((10.0 + 0.244 * (os2.us_weight_class as f32 - 50.0)) as GlyphUnit) - .font_file_2(id + 4), + self.writer.write_obj(id + 2, FontDescriptor::new(base_font, flags, italic_angle) + .font_bbox(bounding_box) + .ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32)) + .descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32)) + .cap_height(font_unit_to_glyph_unit( + os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32, + )) + .stem_v((10.0 + 0.244 * (os2.us_weight_class as f32 - 50.0)) as GlyphUnit) + .font_file_2(id + 4) )?; - // Write the CMap, which maps glyphs to unicode codepoints. - let mapping = font + // --------------------------------------------- + // Extract information from the cmap table. + + let cmap = CMap::new("Custom", system_info, font .read_table::()? .mapping .iter() - .map(|(&c, &cid)| (cid, c)); + .map(|(&c, &cid)| (cid, c)) + ); - self.writer.write_obj(id + 3, &CMap::new("Custom", system_info, mapping))?; + // Write the CMap, which maps glyphs to unicode codepoints. + self.writer.write_obj(id + 3, &cmap)?; + // --------------------------------------------- // Finally write the subsetted font program. + self.writer.write_obj(id + 4, &FontStream::new(font.data().get_ref()))?; id += 5; diff --git a/src/func.rs b/src/func.rs index a7cdfe2fb..ce67034f2 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,25 +1,33 @@ -//! Helper types and macros for creating custom functions. +//! Trait and prelude for custom functions. use crate::syntax::{ParseContext, Parsed}; use crate::syntax::func::FuncHeader; use crate::syntax::span::Spanned; +/// Types that are useful for creating your own functions. pub mod prelude { + pub use crate::{function, body, err}; pub use crate::layout::prelude::*; - pub use crate::layout::{LayoutContext, Commands, layout}; pub use crate::layout::Command::{self, *}; pub use crate::style::{LayoutStyle, PageStyle, TextStyle}; pub use crate::syntax::SyntaxModel; pub use crate::syntax::expr::*; pub use crate::syntax::func::*; - pub use crate::syntax::func::keys::*; - pub use crate::syntax::func::values::*; pub use crate::syntax::span::{Span, Spanned}; } /// Parse a function from source code. pub trait ParseFunc { + /// A metadata type whose value is passed into the function parser. This + /// allows a single function to do different things depending on the value + /// that needs to be given when inserting the function into a + /// [scope](crate::syntax::Scope). + /// + /// For example, the functions `word.spacing`, `line.spacing` and + /// `par.spacing` are actually all the same function + /// [`ContentSpacingFunc`](crate::library::ContentSpacingFunc) with the + /// metadata specifiy which content should be spaced. type Meta: Clone; /// Parse the header and body into this function given a context. @@ -31,6 +39,49 @@ pub trait ParseFunc { ) -> Parsed where Self: Sized; } +/// Allows to implement a function type concisely. +/// +/// # Example +/// A function that hides its body depending on a boolean argument. +/// ``` +/// use typstc::func::prelude::*; +/// +/// function! { +/// #[derive(Debug, Clone, PartialEq)] +/// pub struct HiderFunc { +/// body: Option, +/// } +/// +/// parse(header, body, ctx, errors, decos) { +/// let body = body!(opt: body, ctx, errors, decos); +/// let hidden = header.args.pos.get::(errors) +/// .or_missing(errors, header.name.span, "hidden") +/// .unwrap_or(false); +/// +/// HiderFunc { body: if hidden { None } else { body } } +/// } +/// +/// layout(self, ctx, errors) { +/// match &self.body { +/// Some(model) => vec![LayoutSyntaxModel(model)], +/// None => vec![], +/// } +/// } +/// } +/// ``` +/// This function can be used as follows: +/// ```typst +/// [hider: true][Hi, you.] => Nothing +/// [hider: false][Hi, you.] => Text: "Hi, you." +/// +/// [hider][Hi, you.] => Text: "Hi, you." +/// ^^^^^ +/// missing argument: hidden +/// ``` +/// +/// # More examples +/// Look at the source code of the [`library`](crate::library) module for more +/// examples on how the macro works. #[macro_export] macro_rules! function { // Entry point. @@ -118,8 +169,15 @@ macro_rules! function { /// Parse the body of a function. /// -/// - If the function does not expect a body, use `parse!(nope: body, errors)`. -/// - If the function can have a body, use `parse!(opt: body, ctx, errors, decos)`. +/// - If the function does not expect a body, use `body!(nope: body, errors)`. +/// - If the function can have a body, use `body!(opt: body, ctx, errors, decos)`. +/// +/// # Arguments +/// - The `$body` should be of type `Option>`. +/// - The `$ctx` is the [`ParseContext`](crate::syntax::ParseContext) to use for parsing. +/// - The `$errors` and `$decos` should be mutable references to vectors of spanned +/// errors / decorations which are filled with the errors and decorations arising +/// from parsing. #[macro_export] macro_rules! body { (opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({ @@ -142,12 +200,23 @@ macro_rules! body { }; } -/// Construct an error with optional severity and span. +/// Construct an error with formatted message and optionally severity and / or +/// span. /// /// # Examples /// ``` +/// # use typstc::err; +/// # use typstc::syntax::span::Span; +/// # let span = Span::ZERO; +/// # let value = 0; +/// +/// // With span and default severity `Error`. /// err!(span; "the wrong {}", value); +/// +/// // With no span and severity `Warning`. /// err!(@Warning: span; "non-fatal!"); +/// +/// // Without span and default severity. /// err!("no spans here ..."); /// ``` #[macro_export] diff --git a/src/layout/actions.rs b/src/layout/actions.rs index 710d92b4c..b61f2201f 100644 --- a/src/layout/actions.rs +++ b/src/layout/actions.rs @@ -1,4 +1,4 @@ -//! Drawing and cofiguration actions composing layouts. +//! Drawing and configuration actions composing layouts. use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; @@ -9,14 +9,15 @@ use super::{Layout, Serialize}; use self::LayoutAction::*; -/// A layouting action. +/// A layouting action, which is the basic building block layouts are composed +/// of. #[derive(Clone)] pub enum LayoutAction { /// Move to an absolute position. MoveAbsolute(Size2D), - /// Set the font by index and font size. + /// Set the font given the index from the font loader and font size. SetFont(FontIndex, Size), - /// Write text starting at the current position. + /// Write text at the current position. WriteText(String), /// Visualize a box for debugging purposes. DebugBox(Size2D), @@ -50,17 +51,18 @@ debug_display!(LayoutAction); /// A sequence of layouting actions. /// /// The sequence of actions is optimized as the actions are added. For example, -/// a font changing option will only be added if the selected font is not already active. -/// All configuration actions (like moving, setting fonts, ...) are only flushed when -/// content is written. +/// a font changing option will only be added if the selected font is not +/// already active. All configuration actions (like moving, setting fonts, ...) +/// are only flushed when content is written. /// -/// Furthermore, the action list can translate absolute position into a coordinate system -/// with a different origin. This is realized in the `add_box` method, which allows a layout to -/// be added at a position, effectively translating all movement actions inside the layout -/// by the position. +/// Furthermore, the action list can translate absolute position into a +/// coordinate system with a different origin. This is realized in the +/// `add_layout` method, which allows a layout to be added at a position, +/// effectively translating all movement actions inside the layout by the +/// position. #[derive(Debug, Clone)] pub struct LayoutActions { - pub origin: Size2D, + origin: Size2D, actions: Vec, active_font: (FontIndex, Size), next_pos: Option, @@ -97,15 +99,14 @@ impl LayoutActions { } /// Add a series of actions. - pub fn extend(&mut self, actions: I) - where I: IntoIterator { + pub fn extend(&mut self, actions: I) where I: IntoIterator { for action in actions.into_iter() { self.add(action); } } - /// Add a layout at a position. All move actions inside the layout are translated - /// by the position. + /// Add a layout at a position. All move actions inside the layout are + /// translated by the position. pub fn add_layout(&mut self, position: Size2D, layout: Layout) { self.flush_position(); @@ -120,10 +121,9 @@ impl LayoutActions { self.actions.is_empty() } - /// Return the list of actions as a vector, leaving an empty - /// vector in its position. - pub fn to_vec(&mut self) -> Vec { - std::mem::replace(&mut self.actions, vec![]) + /// Return the list of actions as a vector. + pub fn into_vec(self) -> Vec { + self.actions } /// Append a cached move action if one is cached. diff --git a/src/layout/line.rs b/src/layout/line.rs index 5e0839b1a..2c8e45f28 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -1,9 +1,18 @@ +//! The line layouter arranges boxes into lines. +//! +//! Along the primary axis, the boxes are laid out next to each other while they +//! fit into a line. When a line break is necessary, the line is finished and a +//! new line is started offset on the secondary axis by the height of previous +//! line and the extra line spacing. +//! +//! Internally, the line layouter uses a stack layouter to arrange the finished +//! lines. + use super::stack::{StackLayouter, StackContext}; use super::*; -/// The line layouter arranges boxes next to each other along a primary axis -/// and arranges the resulting lines using an underlying stack layouter. +/// Performs the line layouting. #[derive(Debug, Clone)] pub struct LineLayouter { /// The context for layouting. @@ -34,7 +43,9 @@ pub struct LineContext { pub line_spacing: Size, } -/// A simple line of boxes. +/// A line run is a sequence of boxes with the same alignment that are arranged +/// in a line. A real line can consist of multiple runs with different +/// alignments. #[derive(Debug, Clone)] struct LineRun { /// The so-far accumulated layouts in the line. @@ -43,9 +54,13 @@ struct LineRun { /// line. size: Size2D, /// The alignment of all layouts in the line. + /// + /// When a new run is created the alignment is yet to be determined. Once a + /// layout is added, it is decided which alignment the run has and all + /// further elements of the run must have this alignment. alignment: Option, - /// The remaining usable space if another differently aligned line run - /// already took up some space. + /// If another line run with different alignment already took up some space + /// of the line, this run has less space and how much is stored here. usable: Option, /// A possibly cached soft spacing or spacing state. last_spacing: LastSpacing, @@ -137,7 +152,10 @@ impl LineLayouter { } } - /// The remaining usable size in the run. + /// The remaining usable size of the run. + /// + /// This specifies how much more fits before a line break needs to be + /// issued. fn usable(&self) -> Size2D { // The base is the usable space per stack layouter. let mut usable = self.stack.usable().generalized(self.ctx.axes); @@ -152,7 +170,7 @@ impl LineLayouter { usable } - /// Add primary spacing to the line. + /// Add spacing along the primary axis to the line. pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) { match kind { // A hard space is simply an empty box. @@ -178,20 +196,20 @@ impl LineLayouter { } } - /// Finish the run and add secondary spacing to the underlying stack. + /// Finish the line and add secondary spacing to the underlying stack. pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) { self.finish_line_if_not_empty(); self.stack.add_spacing(spacing, kind) } - /// Change the layouting axes used by this layouter. + /// Update the layouting axes used by this layouter. pub fn set_axes(&mut self, axes: LayoutAxes) { self.finish_line_if_not_empty(); self.ctx.axes = axes; self.stack.set_axes(axes) } - /// Change the layouting spaces to use. + /// Update the layouting spaces to use. /// /// If `replace_empty` is true, the current space is replaced if there are /// no boxes laid into it yet. Otherwise, only the followup spaces are @@ -200,12 +218,14 @@ impl LineLayouter { self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); } - /// Change the line spacing. + /// Update the line spacing. pub fn set_line_spacing(&mut self, line_spacing: Size) { self.ctx.line_spacing = line_spacing; } - /// The remaining unpadded, unexpanding spaces. + /// The remaining inner layout spaces. Inner means, that padding is already + /// subtracted and the spaces are unexpanding. This can be used to signal + /// a function how much space it has to layout itself. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = self.stack.remaining(); *spaces[0].dimensions.get_secondary_mut(self.ctx.axes) @@ -218,19 +238,21 @@ impl LineLayouter { self.run.size == Size2D::ZERO && self.run.layouts.is_empty() } - /// Finish the last line and compute the final multi-layout. + /// Finish the last line and compute the final list of boxes. pub fn finish(mut self) -> MultiLayout { self.finish_line_if_not_empty(); self.stack.finish() } /// Finish the currently active space and start a new one. + /// + /// At the top level, this is a page break. pub fn finish_space(&mut self, hard: bool) { self.finish_line_if_not_empty(); self.stack.finish_space(hard) } - /// Add the current line to the stack and start a new line. + /// Finish the line and start a new one. pub fn finish_line(&mut self) { let mut actions = LayoutActions::new(); @@ -251,7 +273,7 @@ impl LineLayouter { dimensions: self.run.size.specialized(self.ctx.axes), alignment: self.run.alignment .unwrap_or(LayoutAlignment::new(Origin, Origin)), - actions: actions.to_vec(), + actions: actions.into_vec(), }); self.run = LineRun::new(); diff --git a/src/layout/mod.rs b/src/layout/mod.rs index f8074524a..bcabf7f30 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,4 +1,4 @@ -//! The core layouting engine. +//! Layouting types and engines. use std::io::{self, Write}; use std::fmt::{self, Display, Formatter}; @@ -6,7 +6,7 @@ use smallvec::SmallVec; use toddle::query::FontIndex; use crate::size::{Size, Size2D, SizeBox}; -use self::{GenericAxis::*, SpecificAxis::*, Direction::*, Alignment::*}; +use self::prelude::*; pub mod line; pub mod stack; @@ -15,8 +15,13 @@ pub mod text; pub_use_mod!(actions); pub_use_mod!(model); +/// Basic types used across the layouting engine. pub mod prelude { - pub use super::{LayoutSpace, LayoutExpansion, LayoutAxes, LayoutAlignment}; + pub use super::{ + LayoutContext, layout, LayoutSpace, + Layouted, Commands, + LayoutAxes, LayoutAlignment, LayoutExpansion + }; pub use super::GenericAxis::{self, *}; pub use super::SpecificAxis::{self, *}; pub use super::Direction::{self, *}; @@ -27,7 +32,7 @@ pub mod prelude { /// A collection of layouts. pub type MultiLayout = Vec; -/// A sequence of layouting actions inside a box. +/// A finished box with content at fixed positions. #[derive(Debug, Clone)] pub struct Layout { /// The size of the box. @@ -81,10 +86,11 @@ impl Serialize for MultiLayout { } } -/// A possibly stack-allocated vector of layout spaces. +/// A vector of layout spaces, that is stack allocated as long as it only +/// contains at most 2 spaces. pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>; -/// Spacial layouting constraints. +/// The space into which content is laid out. #[derive(Debug, Copy, Clone)] pub struct LayoutSpace { /// The maximum size of the box to layout in. @@ -92,8 +98,7 @@ pub struct LayoutSpace { /// Padding that should be respected on each side. pub padding: SizeBox, /// Whether to expand the dimensions of the resulting layout to the full - /// dimensions of this space or to shrink them to fit the content for the - /// horizontal and vertical axis. + /// dimensions of this space or to shrink them to fit the content. pub expansion: LayoutExpansion, } @@ -119,39 +124,75 @@ impl LayoutSpace { } } -/// Whether to fit to content or expand to the space's size. +/// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LayoutExpansion { - pub horizontal: bool, - pub vertical: bool, +pub enum GenericAxis { + /// The primary axis along which words are laid out. + Primary, + /// The secondary axis along which lines and paragraphs are laid out. + Secondary, } -impl LayoutExpansion { - pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { - LayoutExpansion { horizontal, vertical } +impl GenericAxis { + /// The specific version of this axis in the given system of axes. + pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { + axes.get(self).axis() } +} - /// Borrow the specified component mutably. - pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { - match axis { - Horizontal => &mut self.horizontal, - Vertical => &mut self.vertical, +impl Display for GenericAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Primary => write!(f, "primary"), + Secondary => write!(f, "secondary"), } } } -/// The axes along which the content is laid out. +/// The two specific layouting axes. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum SpecificAxis { + /// The horizontal layouting axis. + Horizontal, + /// The vertical layouting axis. + Vertical, +} + +impl SpecificAxis { + /// The generic version of this axis in the given system of axes. + pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { + if self == axes.primary.axis() { Primary } else { Secondary } + } +} + +impl Display for SpecificAxis { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + Horizontal => write!(f, "horizontal"), + Vertical => write!(f, "vertical"), + } + } +} + +/// Specifies along which directions content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAxes { + /// The primary layouting direction. pub primary: Direction, + /// The secondary layouting direction. pub secondary: Direction, } impl LayoutAxes { + /// Create a new instance from the two values. + /// + /// # Panics + /// This function panics if the directions are aligned, that is, they are + /// on the same axis. pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes { if primary.axis() == secondary.axis() { - panic!("LayoutAxes::new: invalid aligned axes {:?} and {:?}", - primary, secondary); + panic!("LayoutAxes::new: invalid aligned axes \ + {} and {}", primary, secondary); } LayoutAxes { primary, secondary } @@ -172,77 +213,11 @@ impl LayoutAxes { Secondary => &mut self.secondary, } } - - /// Return the direction of the specified specific axis. - pub fn get_specific(self, axis: SpecificAxis) -> Direction { - self.get(axis.to_generic(self)) - } -} - -/// The two generic layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum GenericAxis { - Primary, - Secondary, -} - -impl GenericAxis { - /// The specific version of this axis in the given system of axes. - pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis { - axes.get(self).axis() - } - - /// The other axis. - pub fn inv(self) -> GenericAxis { - match self { - Primary => Secondary, - Secondary => Primary, - } - } -} - -impl Display for GenericAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Primary => write!(f, "primary"), - Secondary => write!(f, "secondary"), - } - } -} - -/// The two specific layouting axes. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum SpecificAxis { - Horizontal, - Vertical, -} - -impl SpecificAxis { - /// The generic version of this axis in the given system of axes. - pub fn to_generic(self, axes: LayoutAxes) -> GenericAxis { - if self == axes.primary.axis() { Primary } else { Secondary } - } - - /// The other axis. - pub fn inv(self) -> SpecificAxis { - match self { - Horizontal => Vertical, - Vertical => Horizontal, - } - } -} - -impl Display for SpecificAxis { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Horizontal => write!(f, "horizontal"), - Vertical => write!(f, "vertical"), - } - } } /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[allow(missing_docs)] pub enum Direction { LeftToRight, RightToLeft, @@ -260,6 +235,8 @@ impl Direction { } /// Whether this axis points into the positive coordinate direction. + /// + /// The positive directions are left-to-right and top-to-bottom. pub fn is_positive(self) -> bool { match self { LeftToRight | TopToBottom => true, @@ -267,6 +244,14 @@ impl Direction { } } + /// The factor for this direction. + /// + /// - `1` if the direction is positive. + /// - `-1` if the direction is negative. + pub fn factor(self) -> i32 { + if self.is_positive() { 1 } else { -1 } + } + /// The inverse axis. pub fn inv(self) -> Direction { match self { @@ -276,14 +261,6 @@ impl Direction { BottomToTop => TopToBottom, } } - - /// The factor for this direction. - /// - /// - `1` if the direction is positive. - /// - `-1` if the direction is negative. - pub fn factor(self) -> i32 { - if self.is_positive() { 1 } else { -1 } - } } impl Display for Direction { @@ -297,18 +274,29 @@ impl Display for Direction { } } -/// Where to align a layout in a container. +/// Specifies where to align a layout in a parent container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlignment { + /// The alignment along the primary axis. pub primary: Alignment, + /// The alignment along the secondary axis. pub secondary: Alignment, } impl LayoutAlignment { + /// Create a new instance from the two values. pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment { LayoutAlignment { primary, secondary } } + /// Return the alignment of the specified generic axis. + pub fn get(self, axis: GenericAxis) -> Alignment { + match axis { + Primary => self.primary, + Secondary => self.secondary, + } + } + /// Borrow the alignment of the specified generic axis mutably. pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment { match axis { @@ -321,8 +309,11 @@ impl LayoutAlignment { /// Where to align content. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Alignment { + /// Align content at the start of the axis. Origin, + /// Align content centered on the axis. Center, + /// Align content at the end of the axis. End, } @@ -337,12 +328,53 @@ impl Alignment { } } -/// Whitespace between boxes with different interaction properties. +/// Specifies whether to expand a layout to the full size of the space it is +/// laid out in or to shrink it to fit the content. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub struct LayoutExpansion { + /// Whether to expand on the horizontal axis. + pub horizontal: bool, + /// Whether to expand on the vertical axis. + pub vertical: bool, +} + +impl LayoutExpansion { + /// Create a new instance from the two values. + pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { + LayoutExpansion { horizontal, vertical } + } + + /// Return the expansion value for the given specific axis. + pub fn get(self, axis: SpecificAxis) -> bool { + match axis { + Horizontal => self.horizontal, + Vertical => self.vertical, + } + } + + /// Borrow the expansion value for the given specific axis mutably. + pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool { + match axis { + Horizontal => &mut self.horizontal, + Vertical => &mut self.vertical, + } + } +} + +/// Defines how a given spacing interacts with (possibly existing) surrounding +/// spacing. +/// +/// There are two options for interaction: Hard and soft spacing. Typically, +/// hard spacing is used when a fixed amount of space needs to be inserted no +/// matter what. In contrast, soft spacing can be used to insert a default +/// spacing between e.g. two words or paragraphs that can still be overridden by +/// a hard space. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum SpacingKind { - /// A hard space consumes surrounding soft spaces and is always layouted. + /// Hard spaces are always laid out and consume surrounding soft space. Hard, - /// A soft space consumes surrounding soft spaces with higher value. + /// Soft spaces are not laid out if they are touching a hard space and + /// consume neighbouring soft spaces with higher levels. Soft(u32), } @@ -357,11 +389,16 @@ impl SpacingKind { pub const WORD: SpacingKind = SpacingKind::Soft(1); } -/// The last appeared spacing. +/// The spacing kind of the most recently inserted item in a layouting process. +/// This is not about the last _spacing item_, but the last _item_, which is why +/// this can be `None`. #[derive(Debug, Copy, Clone, PartialEq)] enum LastSpacing { + /// The last item was hard spacing. Hard, + /// The last item was soft spacing with the given width and level. Soft(Size, u32), + /// The last item was not spacing. None, } diff --git a/src/layout/model.rs b/src/layout/model.rs index 2e61b4536..1d635f5c7 100644 --- a/src/layout/model.rs +++ b/src/layout/model.rs @@ -1,3 +1,7 @@ +//! The model layouter layouts models (i.e. +//! [syntax models](crate::syntax::SyntaxModel) and [functions](crate::func)) +//! by executing commands issued by the models. + use std::future::Future; use std::pin::Pin; use smallvec::smallvec; @@ -13,7 +17,7 @@ use super::text::{layout_text, TextContext}; use super::*; -#[derive(Debug, Clone)] +/// Performs the model layouting. pub struct ModelLayouter<'a, 'p> { ctx: LayoutContext<'a, 'p>, layouter: LineLayouter, @@ -21,7 +25,7 @@ pub struct ModelLayouter<'a, 'p> { errors: Errors, } -/// The general context for layouting. +/// The context for layouting. #[derive(Debug, Clone)] pub struct LayoutContext<'a, 'p> { /// The font loader to retrieve fonts from when typesetting text @@ -46,53 +50,74 @@ pub struct LayoutContext<'a, 'p> { pub debug: bool, } +/// The result of layouting: Some layouted things and a list of errors. pub struct Layouted { + /// The result of the layouting process. pub output: T, + /// Errors that arose in the process of layouting. pub errors: Errors, } -impl Layouted { - pub fn map(self, f: F) -> Layouted where F: FnOnce(T) -> U { - Layouted { - output: f(self.output), - errors: self.errors, - } - } -} - /// A sequence of layouting commands. pub type Commands<'a> = Vec>; -/// Layouting commands from functions to the typesetting engine. +/// Commands issued to the layouting engine by models. #[derive(Debug)] pub enum Command<'a> { + /// Layout the given model in the current context (i.e. not nested). The + /// content of the model is not laid out into a separate box and then added, + /// but simply laid out flat in the active layouting process. + /// + /// This has the effect that the content fits nicely into the active line + /// layouting, enabling functions to e.g. change the style of some piece of + /// text while keeping it integrated in the current paragraph. LayoutSyntaxModel(&'a SyntaxModel), + /// Add a already computed layout. Add(Layout), + /// Add multiple layouts, one after another. This is equivalent to multiple + /// [Add](Command::Add) commands. AddMultiple(MultiLayout), + + /// Add spacing of given [kind](super::SpacingKind) along the primary or + /// secondary axis. The spacing kind defines how the spacing interacts with + /// surrounding spacing. AddSpacing(Size, SpacingKind, GenericAxis), - FinishLine, - FinishSpace, + /// Start a new line. + BreakLine, + /// Start a new paragraph. BreakParagraph, + /// Start a new page, which will exist in the finished layout even if it + /// stays empty (since the page break is a _hard_ space break). BreakPage, + /// Update the text style. SetTextStyle(TextStyle), + /// Update the page style. SetPageStyle(PageStyle), + + /// Update the alignment for future boxes added to this layouting process. SetAlignment(LayoutAlignment), + /// Update the layouting axes along which future boxes will be laid out. + /// This finishes the current line. SetAxes(LayoutAxes), } +/// Layout a syntax model into a list of boxes. pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted { let mut layouter = ModelLayouter::new(ctx); layouter.layout_syntax_model(model).await; layouter.finish() } +/// A dynamic future type which allows recursive invocation of async functions +/// when used as the return type. This is also how the async trait functions +/// work internally. pub type DynFuture<'a, T> = Pin + 'a>>; impl<'a, 'p> ModelLayouter<'a, 'p> { - /// Create a new syntax tree layouter. + /// Create a new model layouter. pub fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> { ModelLayouter { layouter: LineLayouter::new(LineContext { @@ -109,10 +134,12 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } } + /// Flatly layout a model into this layouting process. pub fn layout<'r>( &'r mut self, model: Spanned<&'r dyn Model> ) -> DynFuture<'r, ()> { Box::pin(async move { + // Execute the model's layout function which generates the commands. let layouted = model.v.layout(LayoutContext { style: &self.style, spaces: self.layouter.remaining(), @@ -121,14 +148,16 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { .. self.ctx }).await; - let commands = layouted.output; + // Add the errors generated by the model to the error list. self.errors.extend(offset_spans(layouted.errors, model.span.start)); - for command in commands { + for command in layouted.output { self.execute_command(command, model.span).await; } }) } + /// Layout a syntax model by directly processing the nodes instead of using + /// the command based architecture. pub fn layout_syntax_model<'r>( &'r mut self, model: &'r SyntaxModel @@ -162,6 +191,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } }) } + /// Compute the finished list of boxes. pub fn finish(self) -> Layouted { Layouted { output: self.layouter.finish(), @@ -169,6 +199,8 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } } + /// Execute a command issued by a model. When the command is errorful, the + /// given span is stored with the error. fn execute_command<'r>( &'r mut self, command: Command<'r>, @@ -186,8 +218,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { Secondary => self.layouter.add_secondary_spacing(space, kind), } - FinishLine => self.layouter.finish_line(), - FinishSpace => self.layouter.finish_space(true), + BreakLine => self.layouter.finish_line(), BreakParagraph => self.layout_paragraph(), BreakPage => { if self.ctx.nested { @@ -209,6 +240,9 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } else { self.style.page = style; + // The line layouter has no idea of page styles and thus we + // need to recompute the layouting space resulting of the + // new page style and update it within the layouter. let margins = style.margins(); self.ctx.base = style.dimensions.unpadded(margins); self.layouter.set_spaces(smallvec![ @@ -229,6 +263,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { } }) } + /// Layout a continous piece of text and add it to the line layouter. async fn layout_text(&mut self, text: &str) { self.layouter.add(layout_text(text, TextContext { loader: &self.ctx.loader, @@ -238,6 +273,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { }).await) } + /// Add the spacing for a syntactic space node. fn layout_space(&mut self) { self.layouter.add_primary_spacing( self.style.text.word_spacing(), @@ -245,6 +281,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> { ); } + /// Finish the paragraph and add paragraph spacing. fn layout_paragraph(&mut self) { self.layouter.add_secondary_spacing( self.style.text.paragraph_spacing(), diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 96b44d047..8f76ccbf3 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,10 +1,32 @@ +//! The stack layouter arranges boxes along the secondary layouting axis. +//! +//! Individual layouts can be aligned at origin / center / end on both axes and +//! these alignments are with respect to the growable layout space and not the +//! total possible size. +//! +//! This means that a later layout can have influence on the position of an +//! earlier one. Consider, for example, the following code: +//! ```typst +//! [align: right][A word.] +//! [align: left][A sentence with a couple more words.] +//! ``` +//! The resulting layout looks like this: +//! ```text +//! |--------------------------------------| +//! | A word. | +//! | | +//! | A sentence with a couple more words. | +//! |--------------------------------------| +//! ``` +//! The position of the first aligned box thus depends on the length of the +//! sentence in the second box. + use smallvec::smallvec; use crate::size::ValueBox; use super::*; -/// The stack layouter stack boxes onto each other along the secondary layouting -/// axis. +/// Performs the stack layouting. #[derive(Debug, Clone)] pub struct StackLayouter { /// The context for layouting. @@ -222,8 +244,8 @@ impl StackLayouter { } } - /// The remaining unpadded, unexpanding spaces. If a multi-layout is laid - /// out into these spaces, it will fit into this stack. + /// The remaining unpadded, unexpanding spaces. If a function is laid out + /// into these spaces, it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { let dimensions = self.usable(); @@ -257,7 +279,7 @@ impl StackLayouter { self.space.index == self.ctx.spaces.len() - 1 } - /// Compute the finished multi-layout. + /// Compute the finished list of boxes. pub fn finish(mut self) -> MultiLayout { if self.space.hard || !self.space_is_empty() { self.finish_space(false); @@ -373,7 +395,7 @@ impl StackLayouter { self.layouts.push(Layout { dimensions, alignment: self.ctx.alignment, - actions: actions.to_vec(), + actions: actions.into_vec(), }); // ------------------------------------------------------------------ // diff --git a/src/layout/text.rs b/src/layout/text.rs index eb598e2fd..a0f476431 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,3 +1,9 @@ +//! The text layouter layouts continous pieces of text into boxes. +//! +//! The layouter picks the most suitable font for each individual character. +//! When the primary layouting axis horizontally inversed, the word is spelled +//! backwards. Vertical word layout is not yet supported. + use toddle::query::{SharedFontLoader, FontQuery, FontIndex}; use toddle::tables::{CharMap, Header, HorizontalMetrics}; @@ -6,7 +12,7 @@ use crate::style::TextStyle; use super::*; -/// Layouts text into boxes. +/// Performs the text layouting. struct TextLayouter<'a, 'p> { ctx: TextContext<'a, 'p>, text: &'a str, @@ -17,20 +23,22 @@ struct TextLayouter<'a, 'p> { } /// The context for text layouting. -/// -/// See [`LayoutContext`] for details about the fields. #[derive(Copy, Clone)] pub struct TextContext<'a, 'p> { + /// The font loader to retrieve fonts from when typesetting text + /// using [`layout_text`]. pub loader: &'a SharedFontLoader<'p>, + /// The style for text: Font selection with classes, weights and variants, + /// font sizes, spacing and so on. pub style: &'a TextStyle, + /// The axes along which the word is laid out. For now, only + /// primary-horizontal layouting is supported. pub axes: LayoutAxes, + /// The alignment of the finished layout. pub alignment: LayoutAlignment, } /// Layouts text into a box. -/// -/// There is no complex layout involved. The text is simply laid out left- -/// to-right using the correct font for each character. pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> Layout { TextLayouter::new(text, ctx).layout().await } @@ -48,8 +56,9 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } } - /// Layout the text + /// Do the layouting. async fn layout(mut self) -> Layout { + // If the primary axis is negative, we layout the characters reversed. if self.ctx.axes.primary.is_positive() { for c in self.text.chars() { self.layout_char(c).await; @@ -60,6 +69,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { } } + // Flush the last buffered parts of the word. if !self.buffer.is_empty() { self.actions.add(LayoutAction::WriteText(self.buffer)); } @@ -67,7 +77,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { Layout { dimensions: Size2D::new(self.width, self.ctx.style.font_size()), alignment: self.ctx.alignment, - actions: self.actions.to_vec(), + actions: self.actions.into_vec(), } } @@ -81,6 +91,8 @@ impl<'a, 'p> TextLayouter<'a, 'p> { self.width += char_width; + // Flush the buffer and issue a font setting action if the font differs + // from the last character's one. if self.active_font != index { if !self.buffer.is_empty() { let text = std::mem::replace(&mut self.buffer, String::new()); @@ -106,6 +118,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> { }; if let Some((font, index)) = loader.get(query).await { + // Determine the width of the char. let header = font.read_table::
().ok()?; let font_unit_ratio = 1.0 / (header.units_per_em as f32); let font_unit_to_size = |x| Size::pt(font_unit_ratio * x); diff --git a/src/lib.rs b/src/lib.rs index 946e16d20..a36b5cda5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,27 +7,24 @@ //! be found in the [syntax](crate::syntax) module. //! - **Layouting:** The next step is to transform the syntax tree into a //! portable representation of the typesetted document. Types for these can be -//! found in the [layout] module. A finished layout reading for exporting is a -//! [multi-layout](crate::layout::MultiLayout) consisting of multiple boxes -//! (or pages). +//! found in the [layout](crate::layout) module. A finished layout reading for +//! exporting is a [MultiLayout](crate::layout::MultiLayout) consisting of +//! multiple boxes (or pages). //! - **Exporting:** The finished layout can then be exported into a supported //! format. Submodules for these formats are located in the //! [export](crate::export) module. Currently, the only supported output -//! format is _PDF_. Alternatively, the layout can be serialized to pass it to -//! a suitable renderer. +//! format is [_PDF_](crate::export::pdf). Alternatively, the layout can be +//! serialized to pass it to a suitable renderer. #![allow(unused)] -pub extern crate toddle; +pub use toddle; use std::cell::RefCell; use smallvec::smallvec; - use toddle::query::{FontLoader, FontProvider, SharedFontLoader}; -use crate::layout::MultiLayout; -use crate::layout::prelude::*; -use crate::layout::{LayoutContext, Layouted, layout}; +use crate::layout::{Layouted, MultiLayout}; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::{SyntaxModel, Scope, ParseContext, Parsed, parse}; use crate::syntax::span::Position; @@ -95,8 +92,10 @@ impl<'p> Typesetter<'p> { /// Layout a syntax tree and return the produced layout. pub async fn layout(&self, model: &SyntaxModel) -> Layouted { + use crate::layout::prelude::*; + let margins = self.style.page.margins(); - layout( + crate::layout::layout( &model, LayoutContext { loader: &self.loader, diff --git a/src/library/layout.rs b/src/library/layout.rs index 87e9c3578..d36cd0e07 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,5 +1,4 @@ use crate::size::PSize; -use crate::syntax::func::maps::{AxisMap, PosAxisMap}; use super::*; @@ -21,7 +20,10 @@ function! { layout(self, ctx, errors) { ctx.base = ctx.spaces[0].dimensions; - let map = self.map.dedup(errors, ctx.axes, |alignment| alignment.axis(ctx.axes)); + let map = self.map.dedup(errors, ctx.axes, |alignment| { + alignment.axis().map(|s| s.to_generic(ctx.axes)) + }); + for &axis in &[Primary, Secondary] { if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) { if let Some(generic) = alignment.to_generic(ctx.axes, axis) { diff --git a/src/library/mod.rs b/src/library/mod.rs index b570c48b9..02311c0da 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,4 +1,4 @@ -//! The standard library. +//! The _Typst_ standard library. use crate::syntax::Scope; use crate::func::prelude::*; diff --git a/src/library/page.rs b/src/library/page.rs index 7e135f597..e2a4d687d 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,6 +1,5 @@ use crate::size::Size; use crate::style::{Paper, PaperClass}; -use crate::syntax::func::maps::{AxisMap, PaddingMap}; use super::*; diff --git a/src/library/spacing.rs b/src/library/spacing.rs index 907d5f9bd..b948153d0 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -11,7 +11,7 @@ function! { pub struct LineBreakFunc; parse(default) - layout(self, ctx, errors) { vec![FinishLine] } + layout(self, ctx, errors) { vec![BreakLine] } } function! { @@ -65,8 +65,10 @@ function! { } } -/// The different kinds of content that can be spaced. +/// The different kinds of content that can be spaced. Used as a metadata type +/// for the [`ContentSpacingFunc`]. #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[allow(missing_docs)] pub enum ContentKind { Word, Line, diff --git a/src/size.rs b/src/size.rs index 9dc74dd68..402910179 100644 --- a/src/size.rs +++ b/src/size.rs @@ -95,6 +95,7 @@ impl Sum for Size { /// Either an absolute size or a factor of some entity. #[derive(Copy, Clone, PartialEq)] +#[allow(missing_docs)] pub enum ScaleSize { Absolute(Size), Scaled(f32), diff --git a/src/style.rs b/src/style.rs index 22ed4d2d9..643dc03a1 100644 --- a/src/style.rs +++ b/src/style.rs @@ -7,7 +7,9 @@ use crate::size::{Size, Size2D, SizeBox, ValueBox, PSize}; /// Defines properties of pages and text. #[derive(Debug, Default, Clone)] pub struct LayoutStyle { + /// The style for pages. pub page: PageStyle, + /// The style for text. pub text: TextStyle, } @@ -160,8 +162,9 @@ impl Paper { } } -/// What kind of page this is defines defaults for margins. +/// Paper classes define default margins for a class of related papers. #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[allow(missing_docs)] pub enum PaperClass { Custom, Base, @@ -185,24 +188,28 @@ impl PaperClass { } macro_rules! papers { - ($(($var:ident: $class:expr, $width:expr, $height: expr, $($patterns:tt)*))*) => { - use PaperClass::*; + ($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => { + $(papers!(@$var, stringify!($($pats)*), $class, $width, $height);)* - $(/// The infos for the paper that's in the name. + fn parse_paper(paper: &str) -> Option { + match paper.to_lowercase().as_str() { + $($($pats)* => Some($var),)* + _ => None, + } + } + }; + + (@$var:ident, $names:expr, $class:ident, $width:expr, $height:expr) => { + #[doc = "Paper with the names `"] + #[doc = $names] + #[doc = "`."] pub const $var: Paper = Paper { dimensions: Size2D { x: Size { points: 2.83465 * $width }, y: Size { points: 2.83465 * $height }, }, - class: $class, - };)* - - fn parse_paper(paper: &str) -> Option { - match paper.to_lowercase().as_str() { - $($($patterns)* => Some($var),)* - _ => None, - } - } + class: PaperClass::$class, + }; }; } @@ -259,23 +266,23 @@ papers! { // Unites States // Customary - (PAPER_FOLIO: US, 210.0, 330.0, "folio" | "us-folio" | "us-f4") - (PAPER_LETTER: US, 216.0, 279.0, "letter" | "ansi-a" | - "american-quarto" | "carta") - (PAPER_LEGAL: US, 216.0, 356.0, "legal") + (PAPER_FOLIO: US, 210.0, 330.0, "folio" | "us-folio" | "us-f4") + (PAPER_LETTER: US, 216.0, 279.0, "letter" | "ansi-a" | + "american-quarto" | "carta") + (PAPER_LEGAL: US, 216.0, 356.0, "legal") (PAPER_TABLOID: Newspaper, 279.0, 432.0, "tabloid" | "ansi-b") - (PAPER_LEDGER: Base, 432.0, 279.0, "ledger") - (PAPER_JUNIOR_LEGAL: US, 127.0, 203.0, "junior-legal" | "index-card") - (PAPER_HALF_LETTER: Base, 140.0, 216.0, "half-letter") - (PAPER_GOVERNMENT_LETTER: US, 203.0, 267.0, "government-letter") - (PAPER_GOVERNMENT_LEGAL: US, 216.0, 330.0, "government-legal" | "officio") + (PAPER_LEDGER: Base, 432.0, 279.0, "ledger") + (PAPER_JUNIOR_LEGAL: US, 127.0, 203.0, "junior-legal" | "index-card") + (PAPER_HALF_LETTER: Base, 140.0, 216.0, "half-letter") + (PAPER_GOVERNMENT_LETTER: US, 203.0, 267.0, "government-letter") + (PAPER_GOVERNMENT_LEGAL: US, 216.0, 330.0, "government-legal" | "officio") // ANSI Extensions (PAPER_ANSI_C: Base, 432.0, 559.0, "ansi-c") (PAPER_ANSI_D: Base, 559.0, 864.0, "ansi-d") (PAPER_ANSI_E: Base, 864.0, 1118.0, "ansi-e") (PAPER_ENGINEERING_F: Base, 711.0, 1016.0, "engineering-f" | "engineering" | - "navfac" | "aerospace") + "navfac" | "aerospace") // Architectural Paper (PAPER_ARCH_A: Base, 229.0, 305.0, "arch-a" | "arch-1") @@ -348,9 +355,9 @@ papers! { (PAPER_POT: Base, 310.0, 400.0, "pot" | "ecolier" | "écolier") (PAPER_TELLIERE: Base, 340.0, 440.0, "telliere" | "tellière") (PAPER_COURONNE_ECRITURE: Base, 360.0, 460.0, "couronne-ecriture" | - "couronne" | "couronne-écriture") + "couronne" | "couronne-écriture") (PAPER_COURONNE_EDITION: Base, 370.0, 470.0, "couronne-edition" | - "couronne-édition") + "couronne-édition") (PAPER_ROBERTO: Base, 390.0, 500.0, "roberto") (PAPER_ECU: Base, 400.0, 520.0, "ecu" | "écu") (PAPER_COQUILLE: Base, 440.0, 560.0, "coquille") diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index b4c0dfaa3..879e5faec 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -1,8 +1,10 @@ +//! Expressions in function headers. + use std::fmt::{self, Display, Formatter}; use crate::error::Errors; use crate::size::Size; -use super::func::{keys::Key, values::Value}; +use super::func::{Key, Value}; use super::span::{Span, Spanned}; use super::tokens::is_identifier; @@ -10,16 +12,24 @@ use super::tokens::is_identifier; /// An argument or return value. #[derive(Clone, PartialEq)] pub enum Expr { + /// An identifier: `ident`. Ident(Ident), + /// A string: `"string"`. Str(String), + /// A number: `1.2, 200%`. Number(f64), + /// A size: `2cm, 5.2in`. Size(Size), + /// A bool: `true, false`. Bool(bool), + /// A tuple: `(false, 12cm, "hi")`. Tuple(Tuple), + /// An object: `{ fit: false, size: 12pt }`. Object(Object), } impl Expr { + /// A natural-language name of the type of this expression, e.g. "identifier". pub fn name(&self) -> &'static str { use Expr::*; match self { @@ -34,11 +44,21 @@ impl Expr { } } -/// An identifier. +/// A unicode identifier. +/// +/// The identifier must be valid! This is checked in [`Ident::new`] or +/// [`is_identifier`]. +/// +/// # Example +/// ```typst +/// [func: "hi", ident] +/// ^^^^ ^^^^^ +/// ``` #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Ident(pub String); impl Ident { + /// Create a new identifier from a string checking that it is valid. pub fn new(ident: S) -> Option where S: AsRef + Into { if is_identifier(ident.as_ref()) { Some(Ident(ident.into())) @@ -47,26 +67,37 @@ impl Ident { } } + /// Return a reference to the underlying string. pub fn as_str(&self) -> &str { self.0.as_str() } } -/// A sequence of expressions. +/// An untyped sequence of expressions. +/// +/// # Example +/// ```typst +/// (false, 12cm, "hi") +/// ``` #[derive(Clone, PartialEq)] pub struct Tuple { + /// The elements of the tuple. pub items: Vec>, } impl Tuple { + /// Create an empty tuple. pub fn new() -> Tuple { Tuple { items: vec![] } } + /// Add an element. pub fn add(&mut self, item: Spanned) { self.items.push(item); } + /// Extract (and remove) the first matching value and remove and generate + /// errors for all previous items that did not match. pub fn get(&mut self, errors: &mut Errors) -> Option { while !self.items.is_empty() { let expr = self.items.remove(0); @@ -79,6 +110,8 @@ impl Tuple { None } + /// Extract and return an iterator over all values that match and generate + /// errors for all items that do not match. pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors) -> impl Iterator + 'a { self.items.drain(..).filter_map(move |expr| { @@ -92,36 +125,63 @@ impl Tuple { } /// A key-value collection of identifiers and associated expressions. +/// +/// The pairs themselves are not spanned, but the combined spans can easily be +/// retrieved by merging the spans of key and value as happening in +/// [`FuncArg::span`](super::func::FuncArg::span). +/// +/// # Example +/// ```typst +/// { fit: false, size: 12cm, items: (1, 2, 3) } +/// ``` #[derive(Clone, PartialEq)] pub struct Object { + /// The key-value pairs of the object. pub pairs: Vec, } /// A key-value pair in an object. #[derive(Clone, PartialEq)] pub struct Pair { + /// The key part. + /// ```typst + /// key: value + /// ^^^ + /// ``` pub key: Spanned, + /// The value part. + /// ```typst + /// key: value + /// ^^^^^ + /// ``` pub value: Spanned, } impl Object { + /// Create an empty object. pub fn new() -> Object { Object { pairs: vec![] } } - pub fn add(&mut self, key: Spanned, value: Spanned) { - self.pairs.push(Pair { key, value }); - } - - pub fn add_pair(&mut self, pair: Pair) { + /// Add a pair to object. + pub fn add(&mut self, pair: Pair) { self.pairs.push(pair); } + /// Extract (and remove) a pair with the given key string and matching + /// value. + /// + /// Inserts an error if the value does not match. If the key is not + /// contained, no error is inserted. pub fn get(&mut self, errors: &mut Errors, key: &str) -> Option { let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?; self.get_index::(errors, index) } + /// Extract (and remove) a pair with a matching key and value. + /// + /// Inserts an error if the value does not match. If no matching key is + /// found, no error is inserted. pub fn get_with_key( &mut self, errors: &mut Errors, @@ -135,6 +195,9 @@ impl Object { None } + /// Extract (and remove) all pairs with matching keys and values. + /// + /// Inserts errors for values that do not match. pub fn get_all<'a, K: Key, V: Value>( &'a mut self, errors: &'a mut Errors, @@ -157,6 +220,13 @@ impl Object { }).filter_map(|x| x) } + /// Extract all key value pairs with span information. + /// + /// The spans are over both key and value, like so: + /// ```typst + /// { key: value } + /// ^^^^^^^^^^ + /// ``` pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>( &'a mut self, errors: &'a mut Errors, @@ -165,6 +235,8 @@ impl Object { .map(|(k, v)| Spanned::new((k.v, v.v), Span::merge(k.span, v.span))) } + /// Extract the argument at the given index and insert an error if the value + /// does not match. fn get_index(&mut self, errors: &mut Errors, index: usize) -> Option { let expr = self.pairs.remove(index).value; let span = expr.span; diff --git a/src/syntax/func/keys.rs b/src/syntax/func/keys.rs index 116cd4e6d..d77447c8c 100644 --- a/src/syntax/func/keys.rs +++ b/src/syntax/func/keys.rs @@ -1,3 +1,5 @@ +//! Key types for identifying keyword arguments. + use crate::layout::prelude::*; use super::values::AlignmentValue::{self, *}; use super::*; @@ -6,10 +8,55 @@ use self::AxisKey::*; use self::PaddingKey::*; - +/// Key types are used to extract keyword arguments from +/// [`Objects`](crate::syntax::expr::Object). They represent the key part of a +/// keyword argument. +/// ```typst +/// [func: key=value] +/// ^^^ +/// ``` +/// +/// A key type has an associated output type, which is returned when parsing +/// this key from a string. Most of the time, the output type is simply the key +/// itself, as in the implementation for the [`AxisKey`]: +/// ``` +/// # use typstc::syntax::func::Key; +/// # use typstc::syntax::span::Spanned; +/// # #[derive(Eq, PartialEq)] enum Axis { Horizontal, Vertical, Primary, Secondary } +/// # #[derive(Eq, PartialEq)] enum AxisKey { Specific(Axis), Generic(Axis) } +/// # use Axis::*; +/// # use AxisKey::*; +/// impl Key for AxisKey { +/// type Output = Self; +/// +/// fn parse(key: Spanned<&str>) -> Option { +/// match key.v { +/// "horizontal" | "h" => Some(Specific(Horizontal)), +/// "vertical" | "v" => Some(Specific(Vertical)), +/// "primary" | "p" => Some(Generic(Primary)), +/// "secondary" | "s" => Some(Generic(Secondary)), +/// _ => None, +/// } +/// } +/// } +/// ``` +/// +/// The axis key would also be useful to identify axes when describing +/// dimensions of objects, as in `width=3cm`, because these are also properties +/// that are stored per axis. However, here the used keyword arguments are +/// actually different (`width` instead of `horizontal`)! Therefore we cannot +/// just use the axis key. +/// +/// To fix this, there is another type [`ExtentKey`] which implements `Key` and +/// has the associated output type axis key. The extent key struct itself has no +/// fields and is only used to extract the axis key. This way, we can specify +/// which argument kind we want without duplicating the type in the background. pub trait Key { + /// The type to parse into. type Output: Eq; + /// Parse a key string into the output type if the string is valid for this + /// key. fn parse(key: Spanned<&str>) -> Option; } @@ -21,6 +68,7 @@ impl Key for Spanned { } } +/// Implements [`Key`] for types that just need to match on strings. macro_rules! key { ($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => { impl Key for $type { @@ -36,8 +84,9 @@ macro_rules! key { }; } -/// An argument key which identifies a layouting axis. +/// A key which identifies a layouting axis. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[allow(missing_docs)] pub enum AxisKey { Generic(GenericAxis), Specific(SpecificAxis), @@ -68,6 +117,8 @@ key!(AxisKey, Self, "secondary" | "s" => Generic(Secondary), ); +/// A key which parses into an [`AxisKey`] but uses typical extent keywords +/// instead of axis keywords, e.g. `width` instead of `horizontal`. pub struct ExtentKey; key!(ExtentKey, AxisKey, @@ -77,8 +128,13 @@ key!(ExtentKey, AxisKey, "secondary-size" | "ss" => Generic(Secondary), ); -/// An argument key which identifies an axis, but allows for positional +/// A key which identifies an axis, but alternatively allows for two positional /// arguments with unspecified axes. +/// +/// This type does not implement `Key` in itself since it cannot be parsed from +/// a string. Rather, [`AxisKeys`](AxisKey) and positional arguments should be +/// parsed separately and mapped onto this key, as happening in the +/// [`PosAxisMap`](super::maps::PosAxisMap). #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum PosAxisKey { /// The first positional argument. diff --git a/src/syntax/func/maps.rs b/src/syntax/func/maps.rs index 8941024e5..eb4c8394e 100644 --- a/src/syntax/func/maps.rs +++ b/src/syntax/func/maps.rs @@ -9,40 +9,46 @@ use super::values::*; use super::*; -/// A deduplicating map type useful for storing possibly redundant arguments. +/// A map which deduplicates redundant arguments. +/// +/// Whenever a duplicate argument is inserted into the map, through the +/// functions `from_iter`, `insert` or `extend` an errors is added to the error +/// list that needs to be passed to those functions. +/// +/// All entries need to have span information to enable the error reporting. #[derive(Debug, Clone, PartialEq)] pub struct DedupMap where K: Eq { map: Vec>, } impl DedupMap where K: Eq { + /// Create a new deduplicating map. pub fn new() -> DedupMap { DedupMap { map: vec![] } } + /// Create a new map from an iterator of spanned keys and values. pub fn from_iter(errors: &mut Errors, iter: I) -> DedupMap where I: IntoIterator> { let mut map = DedupMap::new(); - for Spanned { v: (key, value), span } in iter.into_iter() { - map.insert(errors, key, value, span); - } + map.extend(errors, iter); map } - /// Add a key-value pair. - pub fn insert(&mut self, errors: &mut Errors, key: K, value: V, span: Span) { - if self.map.iter().any(|e| e.v.0 == key) { - errors.push(err!(span; "duplicate argument")); + /// Add a spanned key-value pair. + pub fn insert(&mut self, errors: &mut Errors, entry: Spanned<(K, V)>) { + if self.map.iter().any(|e| e.v.0 == entry.v.0) { + errors.push(err!(entry.span; "duplicate argument")); } else { - self.map.push(Spanned { v: (key, value), span }); + self.map.push(entry); } } - /// Add multiple key-value pairs. + /// Add multiple spanned key-value pairs. pub fn extend(&mut self, errors: &mut Errors, items: I) where I: IntoIterator> { - for Spanned { v: (k, v), span } in items.into_iter() { - self.insert(errors, k, v, span); + for item in items.into_iter() { + self.insert(errors, item); } } @@ -65,16 +71,15 @@ impl DedupMap where K: Eq { } /// Create a new map where keys and values are mapped to new keys and - /// values. - /// - /// Returns an error if a new key is duplicate. + /// values. When the mapping introduces new duplicates, errors are + /// generated. pub fn dedup(&self, errors: &mut Errors, mut f: F) -> DedupMap where F: FnMut(&K, &V) -> (K2, V2), K2: Eq { let mut map = DedupMap::new(); for Spanned { v: (key, value), span } in self.map.iter() { let (key, value) = f(key, value); - map.insert(errors, key, value, *span); + map.insert(errors, Spanned { v: (key, value), span: *span }); } map @@ -86,11 +91,12 @@ impl DedupMap where K: Eq { } } -/// A map for storing a value for two axes given by keyword arguments. +/// A map for storing a value for axes given by keyword arguments. #[derive(Debug, Clone, PartialEq)] pub struct AxisMap(DedupMap); impl AxisMap { + /// Parse an axis map from the object. pub fn parse, VT: Value>( errors: &mut Errors, object: &mut Object, @@ -105,12 +111,13 @@ impl AxisMap { } } -/// A map for extracting values for two axes that are given through two -/// positional or keyword arguments. +/// A map for storing values for axes that are given through a combination of +/// (two) positional and keyword arguments. #[derive(Debug, Clone, PartialEq)] pub struct PosAxisMap(DedupMap); impl PosAxisMap { + /// Parse a positional/axis map from the function arguments. pub fn parse, VT: Value>( errors: &mut Errors, args: &mut FuncArgs, @@ -118,8 +125,8 @@ impl PosAxisMap { let mut map = DedupMap::new(); for &key in &[PosAxisKey::First, PosAxisKey::Second] { - if let Some(value) = args.pos.get::>(errors) { - map.insert(errors, key, value.v, value.span); + if let Some(Spanned { v, span }) = args.pos.get::>(errors) { + map.insert(errors, Spanned { v: (key, v), span }) } } @@ -133,7 +140,8 @@ impl PosAxisMap { PosAxisMap(map) } - /// Deduplicate from positional or specific to generic axes. + /// Deduplicate from positional arguments and keyword arguments for generic + /// or specific axes to just generic axes. pub fn dedup( &self, errors: &mut Errors, @@ -151,17 +159,19 @@ impl PosAxisMap { } } -/// A map for extracting padding for a set of specifications given for all -/// sides, opposing sides or single sides. +/// A map for storing padding given for a combination of all sides, opposing +/// sides or single sides. #[derive(Debug, Clone, PartialEq)] pub struct PaddingMap(DedupMap, Option>); impl PaddingMap { + /// Parse a padding map from the function arguments. pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap { let mut map = DedupMap::new(); - if let Some(psize) = args.pos.get::>>(errors) { - map.insert(errors, PaddingKey::All, psize.v, psize.span); + let all = args.pos.get::>>(errors); + if let Some(Spanned { v, span }) = all { + map.insert(errors, Spanned { v: (PaddingKey::All, v), span }); } let paddings: Vec<_> = args.key @@ -187,8 +197,9 @@ impl PaddingMap { All => All, Both(axis) => Both(axis.to_specific(axes)), Side(axis, alignment) => { + let generic = axis.to_generic(axes); let axis = axis.to_specific(axes); - Side(axis, alignment.to_specific(axes, axis)) + Side(axis, alignment.to_specific(axes, generic)) } }, val) }); diff --git a/src/syntax/func/mod.rs b/src/syntax/func/mod.rs index e66b4d6af..0c5b4447e 100644 --- a/src/syntax/func/mod.rs +++ b/src/syntax/func/mod.rs @@ -1,25 +1,38 @@ +//! Primitives for argument parsing in library functions. + use crate::error::{Error, Errors}; use super::expr::{Expr, Ident, Tuple, Object, Pair}; use super::span::{Span, Spanned}; -pub mod maps; -pub mod keys; -pub mod values; +pub_use_mod!(maps); +pub_use_mod!(keys); +pub_use_mod!(values); +/// The parsed header of a function. #[derive(Debug, Clone, PartialEq)] pub struct FuncHeader { + /// The function name, that is: + /// ```typst + /// [box: w=5cm] + /// ^^^ + /// ``` pub name: Spanned, + /// The arguments passed to the function. pub args: FuncArgs, } +/// The positional and keyword arguments passed to a function. #[derive(Debug, Clone, PartialEq)] pub struct FuncArgs { + /// The positional arguments. pub pos: Tuple, + /// They keyword arguments. pub key: Object, } impl FuncArgs { + /// Create new empty function arguments. pub fn new() -> FuncArgs { FuncArgs { pos: Tuple::new(), @@ -30,40 +43,32 @@ impl FuncArgs { /// Add an argument. pub fn add(&mut self, arg: FuncArg) { match arg { - FuncArg::Pos(item) => self.add_pos(item), - FuncArg::Key(pair) => self.add_key_pair(pair), + FuncArg::Pos(item) => self.pos.add(item), + FuncArg::Key(pair) => self.key.add(pair), } } - /// Add a positional argument. - pub fn add_pos(&mut self, item: Spanned) { - self.pos.add(item); - } - - /// Add a keyword argument. - pub fn add_key(&mut self, key: Spanned, value: Spanned) { - self.key.add(key, value); - } - - /// Add a keyword argument from an existing pair. - pub fn add_key_pair(&mut self, pair: Pair) { - self.key.add_pair(pair); - } - + /// Iterate over all arguments. pub fn into_iter(self) -> impl Iterator { self.pos.items.into_iter().map(|item| FuncArg::Pos(item)) .chain(self.key.pairs.into_iter().map(|pair| FuncArg::Key(pair))) } } +/// Either a positional or keyword argument. #[derive(Debug, Clone, PartialEq)] pub enum FuncArg { + /// A positional argument. Pos(Spanned), + /// A keyword argument. Key(Pair), } impl FuncArg { - /// The span or the value or combined span of key and value. + /// The full span of this argument. + /// + /// In case of a positional argument this is just the span of the expression + /// and in case of a keyword argument the combined span of key and value. pub fn span(&self) -> Span { match self { FuncArg::Pos(item) => item.span, @@ -72,14 +77,17 @@ impl FuncArg { } } +/// Extra methods on [`Options`](Option) used for argument parsing. pub trait OptionExt: Sized { - fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self; + /// Add an error about a missing argument `arg` with the given span if the + /// option is `None`. + fn or_missing(self, errors: &mut Errors, span: Span, arg: &str) -> Self; } impl OptionExt for Option { - fn or_missing(self, errors: &mut Errors, span: Span, what: &str) -> Self { + fn or_missing(self, errors: &mut Errors, span: Span, arg: &str) -> Self { if self.is_none() { - errors.push(err!(span; "missing argument: {}", what)); + errors.push(err!(span; "missing argument: {}", arg)); } self } diff --git a/src/syntax/func/values.rs b/src/syntax/func/values.rs index a767aef65..515d6a43d 100644 --- a/src/syntax/func/values.rs +++ b/src/syntax/func/values.rs @@ -1,3 +1,5 @@ +//! Value types for extracting function arguments. + use std::fmt::{self, Display, Formatter}; use std::marker::PhantomData; use toddle::query::{FontStyle, FontWeight}; @@ -10,9 +12,65 @@ use super::*; use self::AlignmentValue::*; +/// Value types are used to extract the values of positional and keyword +/// arguments from [`Tuples`](crate::syntax::expr::Tuple) and +/// [`Objects`](crate::syntax::expr::Object). They represent the value part of +/// an argument. +/// ```typst +/// [func: value, key=value] +/// ^^^^^ ^^^^^ +/// ``` +/// +/// Similarly to the [`Key`] trait, this trait has an associated output type +/// which the values are parsed into. Most of the time this is just `Self`, as +/// in the implementation for `bool`: +/// ``` +/// # use typstc::err; +/// # use typstc::error::Error; +/// # use typstc::syntax::expr::Expr; +/// # use typstc::syntax::func::Value; +/// # use typstc::syntax::span::Spanned; +/// # struct Bool; /* +/// impl Value for bool { +/// # */ impl Value for Bool { +/// # type Output = bool; /* +/// type Output = Self; +/// # */ +/// +/// fn parse(expr: Spanned) -> Result { +/// match expr.v { +/// Expr::Bool(b) => Ok(b), +/// other => Err(err!("expected bool, found {}", other.name())), +/// } +/// } +/// } +/// ``` +/// +/// However, sometimes the `Output` type is not just `Self`. For example, there +/// is a value called `Defaultable` which acts as follows: +/// ``` +/// # use typstc::syntax::func::{FuncArgs, Defaultable}; +/// # use typstc::size::Size; +/// # let mut args = FuncArgs::new(); +/// # let mut errors = vec![]; +/// args.key.get::>(&mut errors, "size"); +/// ``` +/// This will yield. +/// ```typst +/// [func: size=2cm] => Some(Size::cm(2.0)) +/// [func: size=default] => None +/// ``` +/// +/// The type `Defaultable` has no fields and is only used for extracting the +/// option value. This prevents us from having a `Defaultable` type which is +/// essentially simply a bad [`Option`] replacement without the good utility +/// functions. pub trait Value { + /// The type to parse into. type Output; + /// Parse an expression into this value or return an error if the expression + /// is valid for this value type. fn parse(expr: Spanned) -> Result; } @@ -25,6 +83,7 @@ impl Value for Spanned { } } +/// Implements [`Value`] for types that just need to match on expressions. macro_rules! value { ($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => { impl Value for $type { @@ -57,6 +116,8 @@ value!(ScaleSize, Self, "number or size", Expr::Number(scale) => ScaleSize::Scaled(scale as f32), ); +/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and returns a +/// String. pub struct StringLike; value!(StringLike, String, "identifier or string", @@ -64,15 +125,18 @@ value!(StringLike, String, "identifier or string", Expr::Str(s) => s, ); -pub struct Defaultable(PhantomData); +/// A value type that matches the string `"default"` or a value type `V` and +/// returns `Option::Some(V::Output)` for a value and `Option::None` for +/// `"default"`. +pub struct Defaultable(PhantomData); -impl Value for Defaultable { - type Output = Option; +impl Value for Defaultable { + type Output = Option; fn parse(expr: Spanned) -> Result { match expr.v { Expr::Ident(ident) if ident.as_str() == "default" => Ok(None), - _ => T::parse(expr).map(Some) + _ => V::parse(expr).map(Some) } } } @@ -135,8 +199,12 @@ impl Value for Direction { } } +/// A value type that matches identifiers that are valid alignments like +/// `origin` or `right`. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[allow(missing_docs)] pub enum AlignmentValue { + /// A generic alignment. Align(Alignment), Left, Top, @@ -145,26 +213,26 @@ pub enum AlignmentValue { } impl AlignmentValue { - /// The generic axis this alignment corresponds to in the given system of - /// layouting axes. `None` if the alignment is generic. - pub fn axis(self, axes: LayoutAxes) -> Option { + /// The specific axis this alignment corresponds to. `None` if the alignment + /// is generic. + pub fn axis(self) -> Option { match self { - Left | Right => Some(Horizontal.to_generic(axes)), - Top | Bottom => Some(Vertical.to_generic(axes)), + Left | Right => Some(Horizontal), + Top | Bottom => Some(Vertical), Align(_) => None, } } - /// The generic version of this alignment in the given system of layouting - /// axes. + /// The generic version of this alignment on the given axis in the given + /// system of layouting axes. /// /// Returns `None` if the alignment is invalid for the given axis. pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option { let specific = axis.to_specific(axes); - let start = match axes.get(axis).is_positive() { - true => Origin, - false => End, - }; + let positive = axes.get(axis).is_positive(); + + // The alignment matching the origin of the positive coordinate direction. + let start = if positive { Origin } else { End }; match (self, specific) { (Align(alignment), _) => Some(alignment), @@ -174,10 +242,10 @@ impl AlignmentValue { } } - /// The specific version of this alignment in the given system of layouting - /// axes. - pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentValue { - let direction = axes.get_specific(axis); + /// The specific version of this alignment on the given axis in the given + /// system of layouting axes. + pub fn to_specific(self, axes: LayoutAxes, axis: GenericAxis) -> AlignmentValue { + let direction = axes.get(axis); if let Align(alignment) = self { match (direction, alignment) { (LeftToRight, Origin) | (RightToLeft, End) => Left, diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index d5afbca66..4430f6e8f 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -1,4 +1,4 @@ -//! Tokenization and parsing of source code. +//! Syntax models, parsing and tokenization. use std::any::Any; use std::fmt::Debug; @@ -17,14 +17,19 @@ pub_use_mod!(parsing); pub_use_mod!(tokens); +/// Represents a parsed piece of source that can be layouted and in the future +/// also be queried for information used for refactorings, autocomplete, etc. #[async_trait(?Send)] pub trait Model: Debug + ModelBounds { + /// Layout the model into a sequence of commands processed by a + /// [`ModelLayouter`](crate::layout::ModelLayouter). async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> Layouted>; } /// A tree representation of source code. #[derive(Debug, Clone, PartialEq)] pub struct SyntaxModel { + /// The syntactical elements making up this model. pub nodes: SpanVec, } @@ -50,22 +55,22 @@ impl Model for SyntaxModel { } } -/// A node in the syntax tree. +/// A node in the [syntax model](SyntaxModel). #[derive(Debug, Clone)] pub enum Node { - /// A number of whitespace characters containing less than two newlines. + /// Whitespace containing less than two newlines. Space, - /// Whitespace characters with more than two newlines. + /// Whitespace with more than two newlines. Newline, /// Plain text. Text(String), - /// Italics enabled / disabled. + /// Italics were enabled / disabled. ToggleItalic, - /// Bolder enabled / disabled. + /// Bolder was enabled / disabled. ToggleBolder, - /// Monospace enabled / disabled. + /// Monospace was enabled / disabled. ToggleMonospace, - /// A submodel. + /// A submodel, typically a function invocation. Model(Box), } @@ -85,15 +90,34 @@ impl PartialEq for Node { } } +/// Decorations for semantic syntax highlighting. #[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub enum Decoration { + /// A valid function name: + /// ```typst + /// [box] + /// ^^^ + /// ``` ValidFuncName, + + /// An invalid function name: + /// ```typst + /// [blabla] + /// ^^^^^^ + /// ``` InvalidFuncName, + + /// The key of a keyword argument: + /// ```typst + /// [box: width=5cm] + /// ^^^^^ + /// ``` ArgumentKey, } impl dyn Model { + /// Downcast this model to a concrete type implementing [`Model`]. pub fn downcast(&self) -> Option<&T> where T: Model + 'static { self.as_any().downcast_ref::() } @@ -111,9 +135,19 @@ impl Clone for Box { } } +/// This trait describes bounds necessary for types implementing [`Model`]. It is +/// automatically implemented for all types that are [`Model`], [`PartialEq`], +/// [`Clone`] and `'static`. +/// +/// It is necessary to make models comparable and clonable. pub trait ModelBounds { + /// Convert into a `dyn Any`. fn as_any(&self) -> &dyn Any; + + /// Check for equality with another model. fn bound_eq(&self, other: &dyn Model) -> bool; + + /// Clone into a boxed model trait object. fn bound_clone(&self) -> Box; } diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index 3e3e827f2..9d128a46e 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -1,3 +1,5 @@ +//! Parsing of source code into syntax models. + use crate::error::Errors; use super::expr::*; use super::func::{FuncHeader, FuncArgs, FuncArg}; @@ -14,13 +16,19 @@ pub struct ParseContext<'a> { pub scope: &'a Scope, } +/// The result of parsing: Some parsed thing, errors and decorations for syntax +/// highlighting. pub struct Parsed { + /// The result of the parsing process. pub output: T, + /// Errors that arose in the parsing process. pub errors: Errors, + /// Decorations for semantic syntax highlighting. pub decorations: SpanVec, } impl Parsed { + /// Map the output type and keep errors and decorations. pub fn map(self, f: F) -> Parsed where F: FnOnce(T) -> U { Parsed { output: f(self.output), @@ -30,17 +38,24 @@ impl Parsed { } } +/// Parse source code into a syntax model. +/// +/// All errors and decorations are offset by the `start` position. pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { let mut model = SyntaxModel::new(); let mut errors = Vec::new(); let mut decorations = Vec::new(); + // We always start in body mode. The header tokenization mode is only used + // in the `FuncParser`. let mut tokens = Tokens::new(start, src, TokenizationMode::Body); while let Some(token) = tokens.next() { let span = token.span; let node = match token.v { + // Only at least two newlines mean a _real_ newline indicating a + // paragraph break. Token::Space(newlines) => if newlines >= 2 { Node::Newline } else { @@ -50,6 +65,9 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { let parsed: Parsed = FuncParser::new(header, body, ctx).parse(); + // Collect the errors and decorations from the function parsing, + // but offset their spans by the start of the function since + // they are function-local. errors.extend(offset_spans(parsed.errors, span.start)); decorations.extend(offset_spans(parsed.decorations, span.start)); @@ -79,16 +97,30 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed { ctx: ParseContext<'s>, errors: Errors, decorations: SpanVec, + + /// ```typst + /// [tokens][body] + /// ^^^^^^ + /// ``` tokens: Tokens<'s>, peeked: Option>>>, + + /// The spanned body string if there is a body. The string itself is just + /// the parsed without the brackets, while the span includes the brackets. + /// ```typst + /// [tokens][body] + /// ^^^^^^ + /// ``` body: Option>, } impl<'s> FuncParser<'s> { + /// Create a new function parser. fn new( header: &'s str, body: Option>, @@ -104,11 +136,15 @@ impl<'s> FuncParser<'s> { } } + /// Do the parsing. fn parse(mut self) -> Parsed { let parsed = if let Some(header) = self.parse_func_header() { let name = header.name.v.as_str(); let (parser, deco) = match self.ctx.scope.get_parser(name) { + // A valid function. Ok(parser) => (parser, Decoration::ValidFuncName), + + // The fallback parser was returned. Invalid function. Err(parser) => { self.errors.push(err!(header.name.span; "unknown function")); (parser, Decoration::InvalidFuncName) @@ -139,6 +175,7 @@ impl<'s> FuncParser<'s> { } } + /// Parse the header tokens. fn parse_func_header(&mut self) -> Option { let start = self.pos(); self.skip_whitespace(); @@ -166,6 +203,7 @@ impl<'s> FuncParser<'s> { Some(FuncHeader { name, args }) } + /// Parse the function arguments after a colon. fn parse_func_args(&mut self) -> FuncArgs { let mut args = FuncArgs::new(); @@ -226,7 +264,7 @@ impl<'s> FuncParser<'s> { arg } - /// Parse a atomic or compound (tuple / object) expression. + /// Parse an atomic or compound (tuple / object) expression. fn parse_expr(&mut self) -> Option> { let first = self.peek()?; let spanned = |v| Spanned { v, span: first.span }; @@ -301,7 +339,8 @@ impl<'s> FuncParser<'s> { self.errors.push(err!(Span::at(pos); "expected {}", thing)); } - /// Add a found-error if `found` is some and a positional error, otherwise. + /// Add a expected-found-error if `found` is `Some` and an expected-error + /// otherwise. fn expected_found_or_at( &mut self, thing: &str, @@ -315,7 +354,7 @@ impl<'s> FuncParser<'s> { } /// Consume tokens until the function returns true and only consume the last - /// token if instructed to. + /// token if instructed to so by `eat_match`. fn eat_until(&mut self, mut f: F, eat_match: bool) where F: FnMut(Token<'s>) -> bool { while let Some(token) = self.peek() { @@ -342,11 +381,12 @@ impl<'s> FuncParser<'s> { *self.peeked.get_or_insert_with(|| iter.next()) } + /// Peek at the unspanned value of the next token. fn peekv(&mut self) -> Option> { self.peek().map(Spanned::value) } - /// The position at the end of the last eat token / start of the peekable + /// The position at the end of the last eaten token / start of the peekable /// token. fn pos(&self) -> Position { self.peeked.flatten() diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index 2aae331d9..895ee4982 100644 --- a/src/syntax/scope.rs +++ b/src/syntax/scope.rs @@ -1,3 +1,5 @@ +//! Scopes containing function parsers. + use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; diff --git a/src/syntax/span.rs b/src/syntax/span.rs index f5bd4caf8..ad1358cfd 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -5,18 +5,20 @@ use std::ops::{Add, Sub}; use serde::Serialize; -/// A line-column position in source code. +/// Zero-indexed line-column position in source code. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] pub struct Position { - /// The 0-indexed line (inclusive). + /// The zero-indexed line. pub line: usize, - /// The 0-indexed column (inclusive). + /// The zero-indexed column. pub column: usize, } impl Position { + /// The line 0, column 0 position. pub const ZERO: Position = Position { line: 0, column: 0 }; + /// Crete a new instance from line and column. pub fn new(line: usize, column: usize) -> Position { Position { line, column } } @@ -58,20 +60,25 @@ impl Sub for Position { } } -/// Describes a slice of source code. +/// Locates a slice of source code. #[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)] pub struct Span { + /// The inclusive start position. pub start: Position, + /// The inclusive end position. pub end: Position, } impl Span { + /// A dummy span. pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO }; + /// Create a new span from start and end positions. pub fn new(start: Position, end: Position) -> Span { Span { start, end } } + /// Create a new span with the earlier start and later end position. pub fn merge(a: Span, b: Span) -> Span { Span { start: a.start.min(b.start), @@ -79,14 +86,15 @@ impl Span { } } + /// Create a span including just a single position. pub fn at(pos: Position) -> Span { Span { start: pos, end: pos } } - pub fn expand(&mut self, other: Span) { - *self = Span::merge(*self, other) - } - + /// Offset a span by a start position. + /// + /// This is, for example, used to translate error spans from function local + /// to global. pub fn offset(self, start: Position) -> Span { Span { start: start + self.start, @@ -95,26 +103,32 @@ impl Span { } } -/// Annotates a value with the part of the source code it corresponds to. +/// A value with the span it corresponds to in the source code. #[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)] pub struct Spanned { + /// The value. pub v: T, + /// The corresponding span. pub span: Span, } impl Spanned { + /// Create a new instance from a value and its span. pub fn new(v: T, span: Span) -> Spanned { Spanned { v, span } } + /// Access the value. pub fn value(self) -> T { self.v } + /// Map the value using a function while keeping the span. pub fn map(self, f: F) -> Spanned where F: FnOnce(T) -> V { Spanned { v: f(self.v), span: self.span } } + /// Maps the span while keeping the value. pub fn map_span(mut self, f: F) -> Spanned where F: FnOnce(Span) -> Span { self.span = f(self.span); self @@ -124,6 +138,8 @@ impl Spanned { /// A vector of spanned things. pub type SpanVec = Vec>; +/// [Offset](Span::offset) all spans in a vector of spanned things by a start +/// position. pub fn offset_spans(vec: SpanVec, start: Position) -> impl Iterator> { vec.into_iter().map(move |s| s.map_span(|span| span.offset(start))) } diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 0a8e2f178..40d2a5268 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -16,16 +16,31 @@ pub enum Token<'s> { /// number of newlines that were contained in the whitespace. Space(usize), - /// A line comment with inner string contents `//<&'s str>\n`. + /// A line comment with inner string contents `//\n`. LineComment(&'s str), - /// A block comment with inner string contents `/*<&'s str>*/`. The comment + /// A block comment with inner string contents `/**/`. The comment /// can contain nested block comments. BlockComment(&'s str), - /// A function invocation `[
][]`. + /// A function invocation. Function { + /// The header string: + /// ```typst + /// [header: args][body] + /// ^^^^^^^^^^^^ + /// ``` header: &'s str, + /// The spanned body string: + /// ```typst + /// [header][hello *world*] + /// ^^^^^^^^^^^^^ + /// ``` + /// + /// The span includes the brackets while the string does not. body: Option>, + /// Whether the last closing bracket was present. + /// - `[func]` or `[func][body]` => terminated + /// - `[func` or `[func][body` => not terminated terminated: bool, }, @@ -48,7 +63,12 @@ pub enum Token<'s> { /// An identifier in a function header: `center`. ExprIdent(&'s str), /// A quoted string in a function header: `"..."`. - ExprStr { string: &'s str, terminated: bool }, + ExprStr { + /// The string inside the quotes. + string: &'s str, + /// Whether the closing quote was present. + terminated: bool + }, /// A number in a function header: `3.14`. ExprNumber(f64), /// A size in a function header: `12pt`. @@ -110,13 +130,19 @@ pub struct Tokens<'s> { index: usize, } +/// Whether to tokenize in header mode which yields expression, comma and +/// similar tokens or in body mode which yields text and star, underscore, +/// backtick tokens. #[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[allow(missing_docs)] pub enum TokenizationMode { Header, Body, } impl<'s> Tokens<'s> { + /// Create a new token iterator with the given mode where the first token + /// span starts an the given `start` position. pub fn new(start: Position, src: &'s str, mode: TokenizationMode) -> Tokens<'s> { Tokens { src,