summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-26 15:51:13 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-26 15:51:13 +0100
commit20fb4e7c379b79b84d9884d5f2c89d781c5793e2 (patch)
treea1eef90680afa2b43cb1ce0a687c837fd78810e7
parent0a087cd28bbee5fcdffbb9d49b0ba9f413ad7f92 (diff)
Document everything 📜
-rw-r--r--src/error.rs14
-rw-r--r--src/export/pdf.rs81
-rw-r--r--src/func.rs83
-rw-r--r--src/layout/actions.rs40
-rw-r--r--src/layout/line.rs52
-rw-r--r--src/layout/mod.rs231
-rw-r--r--src/layout/model.rs75
-rw-r--r--src/layout/stack.rs34
-rw-r--r--src/layout/text.rs29
-rw-r--r--src/lib.rs21
-rw-r--r--src/library/layout.rs6
-rw-r--r--src/library/mod.rs2
-rw-r--r--src/library/page.rs1
-rw-r--r--src/library/spacing.rs6
-rw-r--r--src/size.rs1
-rw-r--r--src/style.rs57
-rw-r--r--src/syntax/expr.rs88
-rw-r--r--src/syntax/func/keys.rs62
-rw-r--r--src/syntax/func/maps.rs65
-rw-r--r--src/syntax/func/mod.rs56
-rw-r--r--src/syntax/func/values.rs106
-rw-r--r--src/syntax/mod.rs50
-rw-r--r--src/syntax/parsing.rs48
-rw-r--r--src/syntax/scope.rs2
-rw-r--r--src/syntax/span.rs34
-rw-r--r--src/syntax/tokens.rs34
26 files changed, 924 insertions, 354 deletions
diff --git a/src/error.rs b/src/error.rs
index cd107741..b08d27dd 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<Error>;
+/// 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<String>, severity: Severity) -> Error {
Error { message: message.into(), severity }
}
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 421ffb19..48aa2510 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<W: Write>(
&self,
layout: &MultiLayout,
loader: &SharedFontLoader,
target: W,
- ) -> PdfResult<usize>
- {
+ ) -> PdfResult<usize> {
ExportProcess::new(layout, loader, target)?.write()
}
}
+/// The data relevant to the export of one document.
struct ExportProcess<'d, W: Write> {
writer: PdfWriter<W>,
layouts: &'d MultiLayout,
@@ -66,7 +67,7 @@ struct ExportProcess<'d, W: Write> {
fonts: Vec<OwnedFont>,
}
-/// 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<ExportProcess<'d, W>>
- {
+ ) -> PdfResult<ExportProcess<'d, W>> {
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<OwnedFont>, HashMap<FontIndex, usize>)>
- {
+ ) -> PdfResult<(Vec<OwnedFont>, HashMap<FontIndex, usize>)> {
let mut fonts = Vec::new();
let mut font_chars: HashMap<FontIndex, HashSet<char>> = HashMap::new();
let mut old_to_new: HashMap<FontIndex, usize> = HashMap::new();
let mut new_to_old: HashMap<usize, FontIndex> = 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<usize> {
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::<Name>()?
.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::<Header>()?;
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::<OS2>()?;
// 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::<CharMap>()?
.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 a7cdfe2f..ce67034f 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<Self> 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<SyntaxModel>,
+/// }
+///
+/// parse(header, body, ctx, errors, decos) {
+/// let body = body!(opt: body, ctx, errors, decos);
+/// let hidden = header.args.pos.get::<bool>(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<Spanned<&str>>`.
+/// - 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 710d92b4..b61f2201 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<LayoutAction>,
active_font: (FontIndex, Size),
next_pos: Option<Size2D>,
@@ -97,15 +99,14 @@ impl LayoutActions {
}
/// Add a series of actions.
- pub fn extend<I>(&mut self, actions: I)
- where I: IntoIterator<Item = LayoutAction> {
+ pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item = LayoutAction> {
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<LayoutAction> {
- std::mem::replace(&mut self.actions, vec![])
+ /// Return the list of actions as a vector.
+ pub fn into_vec(self) -> Vec<LayoutAction> {
+ self.actions
}
/// Append a cached move action if one is cached.
diff --git a/src/layout/line.rs b/src/layout/line.rs
index 5e0839b1..2c8e45f2 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<LayoutAlignment>,
- /// 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<Size>,
/// 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 f8074524..bcabf7f3 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<Layout>;
-/// 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,70 +124,12 @@ impl LayoutSpace {
}
}
-/// Whether to fit to content or expand to the space's size.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct LayoutExpansion {
- pub horizontal: bool,
- pub vertical: bool,
-}
-
-impl LayoutExpansion {
- pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
- LayoutExpansion { horizontal, vertical }
- }
-
- /// 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,
- }
- }
-}
-
-/// The axes along which the content is laid out.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub struct LayoutAxes {
- pub primary: Direction,
- pub secondary: Direction,
-}
-
-impl LayoutAxes {
- pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes {
- if primary.axis() == secondary.axis() {
- panic!("LayoutAxes::new: invalid aligned axes {:?} and {:?}",
- primary, secondary);
- }
-
- LayoutAxes { primary, secondary }
- }
-
- /// Return the direction of the specified generic axis.
- pub fn get(self, axis: GenericAxis) -> Direction {
- match axis {
- Primary => self.primary,
- Secondary => self.secondary,
- }
- }
-
- /// Borrow the direction of the specified generic axis mutably.
- pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction {
- match axis {
- Primary => &mut self.primary,
- 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 {
+ /// The primary axis along which words are laid out.
Primary,
+ /// The secondary axis along which lines and paragraphs are laid out.
Secondary,
}
@@ -191,14 +138,6 @@ impl GenericAxis {
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 {
@@ -213,7 +152,9 @@ impl Display for GenericAxis {
/// 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,
}
@@ -222,14 +163,6 @@ impl SpecificAxis {
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 {
@@ -241,8 +174,50 @@ impl Display for SpecificAxis {
}
}
+/// 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);
+ }
+
+ LayoutAxes { primary, secondary }
+ }
+
+ /// Return the direction of the specified generic axis.
+ pub fn get(self, axis: GenericAxis) -> Direction {
+ match axis {
+ Primary => self.primary,
+ Secondary => self.secondary,
+ }
+ }
+
+ /// Borrow the direction of the specified generic axis mutably.
+ pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Direction {
+ match axis {
+ Primary => &mut self.primary,
+ Secondary => &mut self.secondary,
+ }
+ }
+}
+
/// 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 2e61b453..1d635f5c 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<T> {
+ /// The result of the layouting process.
pub output: T,
+ /// Errors that arose in the process of layouting.
pub errors: Errors,
}
-impl<T> Layouted<T> {
- pub fn map<F, U>(self, f: F) -> Layouted<U> where F: FnOnce(T) -> U {
- Layouted {
- output: f(self.output),
- errors: self.errors,
- }
- }
-}
-
/// A sequence of layouting commands.
pub type Commands<'a> = Vec<Command<'a>>;
-/// 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<MultiLayout> {
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<Box<dyn Future<Output=T> + '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<MultiLayout> {
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 96b44d04..8f76ccbf 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 eb598e2f..a0f47643 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::<Header>().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 946e16d2..a36b5cda 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<MultiLayout> {
+ 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 87e9c357..d36cd0e0 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 b570c48b..02311c0d 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 7e135f59..e2a4d687 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 907d5f9b..b948153d 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 9dc74dd6..40291017 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 22ed4d2d..643dc03a 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,25 +188,29 @@ impl PaperClass {
}
macro_rules! papers {
- ($(($var:ident: $class:expr, $width:expr, $height: expr, $($patterns:tt)*))*) => {
- use PaperClass::*;
-
- $(/// The infos for the paper that's in the name.
- pub const $var: Paper = Paper {
- dimensions: Size2D {
- x: Size { points: 2.83465 * $width },
- y: Size { points: 2.83465 * $height },
- },
- class: $class,
- };)*
+ ($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
+ $(papers!(@$var, stringify!($($pats)*), $class, $width, $height);)*
fn parse_paper(paper: &str) -> Option<Paper> {
match paper.to_lowercase().as_str() {
- $($($patterns)* => Some($var),)*
+ $($($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: PaperClass::$class,
+ };
+ };
}
// All paper sizes in mm.
@@ -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 b4c0dfaa..879e5fae 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<S>(ident: S) -> Option<Ident> where S: AsRef<str> + Into<String> {
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<Spanned<Expr>>,
}
impl Tuple {
+ /// Create an empty tuple.
pub fn new() -> Tuple {
Tuple { items: vec![] }
}
+ /// Add an element.
pub fn add(&mut self, item: Spanned<Expr>) {
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<V: Value>(&mut self, errors: &mut Errors) -> Option<V::Output> {
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<Item=V::Output> + '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<Pair>,
}
/// A key-value pair in an object.
#[derive(Clone, PartialEq)]
pub struct Pair {
+ /// The key part.
+ /// ```typst
+ /// key: value
+ /// ^^^
+ /// ```
pub key: Spanned<Ident>,
+ /// The value part.
+ /// ```typst
+ /// key: value
+ /// ^^^^^
+ /// ```
pub value: Spanned<Expr>,
}
impl Object {
+ /// Create an empty object.
pub fn new() -> Object {
Object { pairs: vec![] }
}
- pub fn add(&mut self, key: Spanned<Ident>, value: Spanned<Expr>) {
- 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<V: Value>(&mut self, errors: &mut Errors, key: &str) -> Option<V::Output> {
let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?;
self.get_index::<V>(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<K: Key, V: Value>(
&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<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V::Output> {
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 116cd4e6..d77447c8 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<Self::Output> {
+/// 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<Self::Output>;
}
@@ -21,6 +68,7 @@ impl<K: Key> Key for Spanned<K> {
}
}
+/// 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 8941024e..eb4c8394 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<K, V> where K: Eq {
map: Vec<Spanned<(K, V)>>,
}
impl<K, V> DedupMap<K, V> where K: Eq {
+ /// Create a new deduplicating map.
pub fn new() -> DedupMap<K, V> {
DedupMap { map: vec![] }
}
+ /// Create a new map from an iterator of spanned keys and values.
pub fn from_iter<I>(errors: &mut Errors, iter: I) -> DedupMap<K, V>
where I: IntoIterator<Item=Spanned<(K, V)>> {
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<I>(&mut self, errors: &mut Errors, items: I)
where I: IntoIterator<Item=Spanned<(K, V)>> {
- 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<K, V> DedupMap<K, V> 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<F, K2, V2>(&self, errors: &mut Errors, mut f: F) -> DedupMap<K2, V2>
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<K, V> DedupMap<K, V> 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<V>(DedupMap<AxisKey, V>);
impl<V: Clone> AxisMap<V> {
+ /// Parse an axis map from the object.
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
errors: &mut Errors,
object: &mut Object,
@@ -105,12 +111,13 @@ impl<V: Clone> AxisMap<V> {
}
}
-/// 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<V>(DedupMap<PosAxisKey, V>);
impl<V: Clone> PosAxisMap<V> {
+ /// Parse a positional/axis map from the function arguments.
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
errors: &mut Errors,
args: &mut FuncArgs,
@@ -118,8 +125,8 @@ impl<V: Clone> PosAxisMap<V> {
let mut map = DedupMap::new();
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
- if let Some(value) = args.pos.get::<Spanned<VT>>(errors) {
- map.insert(errors, key, value.v, value.span);
+ if let Some(Spanned { v, span }) = args.pos.get::<Spanned<VT>>(errors) {
+ map.insert(errors, Spanned { v: (key, v), span })
}
}
@@ -133,7 +140,8 @@ impl<V: Clone> PosAxisMap<V> {
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<F>(
&self,
errors: &mut Errors,
@@ -151,17 +159,19 @@ impl<V: Clone> PosAxisMap<V> {
}
}
-/// 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<PaddingKey<AxisKey>, Option<PSize>>);
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::<Spanned<Defaultable<PSize>>>(errors) {
- map.insert(errors, PaddingKey::All, psize.v, psize.span);
+ let all = args.pos.get::<Spanned<Defaultable<PSize>>>(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 e66b4d6a..0c5b4447 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<Ident>,
+ /// 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<Expr>) {
- self.pos.add(item);
- }
-
- /// Add a keyword argument.
- pub fn add_key(&mut self, key: Spanned<Ident>, value: Spanned<Expr>) {
- 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<Item=FuncArg> {
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<Expr>),
+ /// 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<T> OptionExt for Option<T> {
- 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 a767aef6..515d6a43 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<Expr>) -> Result<Self::Output, Error> {
+/// 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<V>` 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::<Defaultable<Size>>(&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<V>` 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<Expr>) -> Result<Self::Output, Error>;
}
@@ -25,6 +83,7 @@ impl<V: Value> Value for Spanned<V> {
}
}
+/// 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<T>(PhantomData<T>);
+/// 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<V>(PhantomData<V>);
-impl<T: Value> Value for Defaultable<T> {
- type Output = Option<T::Output>;
+impl<V: Value> Value for Defaultable<V> {
+ type Output = Option<V::Output>;
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
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<GenericAxis> {
+ /// The specific axis this alignment corresponds to. `None` if the alignment
+ /// is generic.
+ pub fn axis(self) -> Option<SpecificAxis> {
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<Alignment> {
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 d5afbca6..4430f6e8 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<Commands<'a>>;
}
/// A tree representation of source code.
#[derive(Debug, Clone, PartialEq)]
pub struct SyntaxModel {
+ /// The syntactical elements making up this model.
pub nodes: SpanVec<Node>,
}
@@ -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<dyn Model>),
}
@@ -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<T>(&self) -> Option<&T> where T: Model + 'static {
self.as_any().downcast_ref::<T>()
}
@@ -111,9 +135,19 @@ impl Clone for Box<dyn Model> {
}
}
+/// 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<dyn Model>;
}
diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs
index 3e3e827f..9d128a46 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<T> {
+ /// 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<Decoration>,
}
impl<T> Parsed<T> {
+ /// Map the output type and keep errors and decorations.
pub fn map<F, U>(self, f: F) -> Parsed<U> where F: FnOnce(T) -> U {
Parsed {
output: f(self.output),
@@ -30,17 +38,24 @@ impl<T> Parsed<T> {
}
}
+/// 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<SyntaxModel> {
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<SyntaxMode
Token::Function { header, body, terminated } => {
let parsed: Parsed<Node> = 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<SyntaxMode
Parsed { output: model, errors, decorations }
}
+/// Performs the function parsing.
struct FuncParser<'s> {
ctx: ParseContext<'s>,
errors: Errors,
decorations: SpanVec<Decoration>,
+
+ /// ```typst
+ /// [tokens][body]
+ /// ^^^^^^
+ /// ```
tokens: Tokens<'s>,
peeked: Option<Option<Spanned<Token<'s>>>>,
+
+ /// 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<Spanned<&'s str>>,
}
impl<'s> FuncParser<'s> {
+ /// Create a new function parser.
fn new(
header: &'s str,
body: Option<Spanned<&'s str>>,
@@ -104,11 +136,15 @@ impl<'s> FuncParser<'s> {
}
}
+ /// Do the parsing.
fn parse(mut self) -> Parsed<Node> {
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<FuncHeader> {
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<Spanned<Expr>> {
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<F>(&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<Token<'s>> {
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 2aae331d..895ee498 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 f5bd4caf..ad1358cf 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<T> {
+ /// The value.
pub v: T,
+ /// The corresponding span.
pub span: Span,
}
impl<T> Spanned<T> {
+ /// Create a new instance from a value and its span.
pub fn new(v: T, span: Span) -> Spanned<T> {
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<V, F>(self, f: F) -> Spanned<V> where F: FnOnce(T) -> V {
Spanned { v: f(self.v), span: self.span }
}
+ /// Maps the span while keeping the value.
pub fn map_span<F>(mut self, f: F) -> Spanned<T> where F: FnOnce(Span) -> Span {
self.span = f(self.span);
self
@@ -124,6 +138,8 @@ impl<T> Spanned<T> {
/// A vector of spanned things.
pub type SpanVec<T> = Vec<Spanned<T>>;
+/// [Offset](Span::offset) all spans in a vector of spanned things by a start
+/// position.
pub fn offset_spans<T>(vec: SpanVec<T>, start: Position) -> impl Iterator<Item=Spanned<T>> {
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 0a8e2f17..40d2a526 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 `//<str>\n`.
LineComment(&'s str),
- /// A block comment with inner string contents `/*<&'s str>*/`. The comment
+ /// A block comment with inner string contents `/*<str>*/`. The comment
/// can contain nested block comments.
BlockComment(&'s str),
- /// A function invocation `[<header>][<body>]`.
+ /// 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<Spanned<&'s str>>,
+ /// 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,