mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Document everything 📜
This commit is contained in:
parent
0a087cd28b
commit
20fb4e7c37
14
src/error.rs
14
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 serde::Serialize;
|
||||||
use crate::syntax::span::SpanVec;
|
use crate::syntax::span::SpanVec;
|
||||||
|
|
||||||
|
|
||||||
|
/// A spanned list of errors.
|
||||||
pub type Errors = SpanVec<Error>;
|
pub type Errors = SpanVec<Error>;
|
||||||
|
|
||||||
|
/// An error that arose in parsing or layouting.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
|
/// An error message describing the problem.
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
/// How severe / important the error is.
|
||||||
pub severity: Severity,
|
pub severity: Severity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How severe / important an error is.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize)]
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
|
/// Something in the code is not good.
|
||||||
Warning,
|
Warning,
|
||||||
|
/// Something in the code is wrong!
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
/// Create a new error from message and severity.
|
||||||
pub fn new(message: impl Into<String>, severity: Severity) -> Error {
|
pub fn new(message: impl Into<String>, severity: Severity) -> Error {
|
||||||
Error { message: message.into(), severity }
|
Error { message: message.into(), severity }
|
||||||
}
|
}
|
||||||
|
@ -32,20 +32,21 @@ impl PdfExporter {
|
|||||||
PdfExporter {}
|
PdfExporter {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export a finished multi-layout. The layout needs to have been created with the same
|
/// Export a layouted list of boxes. The same font loader as used for
|
||||||
/// font loader passed in here since the indices must match. The PDF data is written into
|
/// layouting needs to be passed in here since the layout only contains
|
||||||
/// the target writable and the number of bytes written is returned.
|
/// 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>(
|
pub fn export<W: Write>(
|
||||||
&self,
|
&self,
|
||||||
layout: &MultiLayout,
|
layout: &MultiLayout,
|
||||||
loader: &SharedFontLoader,
|
loader: &SharedFontLoader,
|
||||||
target: W,
|
target: W,
|
||||||
) -> PdfResult<usize>
|
) -> PdfResult<usize> {
|
||||||
{
|
|
||||||
ExportProcess::new(layout, loader, target)?.write()
|
ExportProcess::new(layout, loader, target)?.write()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The data relevant to the export of one document.
|
||||||
struct ExportProcess<'d, W: Write> {
|
struct ExportProcess<'d, W: Write> {
|
||||||
writer: PdfWriter<W>,
|
writer: PdfWriter<W>,
|
||||||
layouts: &'d MultiLayout,
|
layouts: &'d MultiLayout,
|
||||||
@ -66,7 +67,7 @@ struct ExportProcess<'d, W: Write> {
|
|||||||
fonts: Vec<OwnedFont>,
|
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 {
|
struct Offsets {
|
||||||
catalog: Ref,
|
catalog: Ref,
|
||||||
page_tree: Ref,
|
page_tree: Ref,
|
||||||
@ -76,12 +77,13 @@ struct Offsets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, W: Write> ExportProcess<'d, W> {
|
impl<'d, W: Write> ExportProcess<'d, W> {
|
||||||
|
/// Prepare the export. Only once [`ExportProcess::write`] is called the
|
||||||
|
/// writing really happens.
|
||||||
fn new(
|
fn new(
|
||||||
layouts: &'d MultiLayout,
|
layouts: &'d MultiLayout,
|
||||||
font_loader: &SharedFontLoader,
|
font_loader: &SharedFontLoader,
|
||||||
target: W,
|
target: W,
|
||||||
) -> PdfResult<ExportProcess<'d, W>>
|
) -> PdfResult<ExportProcess<'d, W>> {
|
||||||
{
|
|
||||||
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
|
let (fonts, font_remap) = Self::subset_fonts(layouts, font_loader)?;
|
||||||
let offsets = Self::calculate_offsets(layouts.len(), fonts.len());
|
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
|
/// Subsets all fonts and assign a new PDF-internal index to each one. The
|
||||||
/// maps the old indices (used by the layouts) to the new one used in the PDF.
|
/// returned hash map maps the old indices (used by the layouts) to the new
|
||||||
/// The new ones index into the returned vector.
|
/// one used in the PDF. The new ones index into the returned vector of
|
||||||
|
/// owned fonts.
|
||||||
fn subset_fonts(
|
fn subset_fonts(
|
||||||
layouts: &'d MultiLayout,
|
layouts: &'d MultiLayout,
|
||||||
font_loader: &SharedFontLoader
|
font_loader: &SharedFontLoader
|
||||||
) -> PdfResult<(Vec<OwnedFont>, HashMap<FontIndex, usize>)>
|
) -> PdfResult<(Vec<OwnedFont>, HashMap<FontIndex, usize>)> {
|
||||||
{
|
|
||||||
let mut fonts = Vec::new();
|
let mut fonts = Vec::new();
|
||||||
let mut font_chars: HashMap<FontIndex, HashSet<char>> = HashMap::new();
|
let mut font_chars: HashMap<FontIndex, HashSet<char>> = HashMap::new();
|
||||||
let mut old_to_new: HashMap<FontIndex, usize> = HashMap::new();
|
let mut old_to_new: HashMap<FontIndex, usize> = HashMap::new();
|
||||||
let mut new_to_old: HashMap<usize, FontIndex> = HashMap::new();
|
let mut new_to_old: HashMap<usize, FontIndex> = HashMap::new();
|
||||||
let mut active_font = FontIndex::MAX;
|
let mut active_font = FontIndex::MAX;
|
||||||
|
|
||||||
// We want to find out which fonts are used at all and which are chars
|
// We want to find out which fonts are used at all and which chars are
|
||||||
// are used for these. We use this information to create subsetted fonts.
|
// used for those. We use this information to create subsetted fonts.
|
||||||
for layout in layouts {
|
for layout in layouts {
|
||||||
for action in &layout.actions {
|
for action in &layout.actions {
|
||||||
match action {
|
match action {
|
||||||
@ -141,11 +143,13 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
let num_fonts = old_to_new.len();
|
let num_fonts = old_to_new.len();
|
||||||
let mut font_loader = font_loader.borrow_mut();
|
let mut font_loader = font_loader.borrow_mut();
|
||||||
|
|
||||||
|
// All tables not listed here are dropped.
|
||||||
const SUBSET_TABLES: [&str; 13] = [
|
const SUBSET_TABLES: [&str; 13] = [
|
||||||
"name", "OS/2", "post", "head", "hhea", "hmtx", "maxp",
|
"name", "OS/2", "post", "head", "hhea", "hmtx", "maxp",
|
||||||
"cmap", "cvt ", "fpgm", "prep", "loca", "glyf",
|
"cmap", "cvt ", "fpgm", "prep", "loca", "glyf",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Do the subsetting.
|
||||||
for index in 0 .. num_fonts {
|
for index in 0 .. num_fonts {
|
||||||
let old_index = new_to_old[&index];
|
let old_index = new_to_old[&index];
|
||||||
let font = font_loader.get_with_index(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))
|
Ok((fonts, old_to_new))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We need to know in advance which IDs to use for which objects to cross-reference them.
|
/// We need to know in advance which IDs to use for which objects to
|
||||||
/// Therefore, we calculate them in the beginning.
|
/// cross-reference them. Therefore, we calculate the indices in the
|
||||||
|
/// beginning.
|
||||||
fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets {
|
fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets {
|
||||||
let catalog = 1;
|
let catalog = 1;
|
||||||
let page_tree = 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> {
|
fn write(&mut self) -> PdfResult<usize> {
|
||||||
self.writer.write_header(Version::new(1, 7))?;
|
self.writer.write_header(Version::new(1, 7))?;
|
||||||
self.write_preface()?;
|
self.write_preface()?;
|
||||||
@ -241,6 +246,8 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
|
|
||||||
/// Write the content of a page.
|
/// Write the content of a page.
|
||||||
fn write_page(&mut self, id: u32, page: &Layout) -> PdfResult<()> {
|
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 text = Text::new();
|
||||||
let mut active_font = (std::usize::MAX, 0.0);
|
let mut active_font = (std::usize::MAX, 0.0);
|
||||||
let mut next_pos = None;
|
let mut next_pos = None;
|
||||||
@ -280,6 +287,8 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
let mut id = self.offsets.fonts.0;
|
let mut id = self.offsets.fonts.0;
|
||||||
|
|
||||||
for font in &mut self.fonts {
|
for font in &mut self.fonts {
|
||||||
|
// ---------------------------------------------
|
||||||
|
// Extract information from the name table.
|
||||||
let name = font
|
let name = font
|
||||||
.read_table::<Name>()?
|
.read_table::<Name>()?
|
||||||
.get_decoded(NameEntry::PostScriptName)
|
.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 head = font.read_table::<Header>()?;
|
||||||
|
|
||||||
let font_unit_ratio = 1.0 / (head.units_per_em as f32);
|
let font_unit_ratio = 1.0 / (head.units_per_em as f32);
|
||||||
@ -356,9 +365,7 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
let os2 = font.read_table::<OS2>()?;
|
let os2 = font.read_table::<OS2>()?;
|
||||||
|
|
||||||
// Write the font descriptor (contains the global information about the font).
|
// Write the font descriptor (contains the global information about the font).
|
||||||
self.writer.write_obj(
|
self.writer.write_obj(id + 2, FontDescriptor::new(base_font, flags, italic_angle)
|
||||||
id + 2,
|
|
||||||
FontDescriptor::new(base_font, flags, italic_angle)
|
|
||||||
.font_bbox(bounding_box)
|
.font_bbox(bounding_box)
|
||||||
.ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32))
|
.ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32))
|
||||||
.descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32))
|
.descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32))
|
||||||
@ -366,19 +373,25 @@ impl<'d, W: Write> ExportProcess<'d, W> {
|
|||||||
os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32,
|
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)
|
.stem_v((10.0 + 0.244 * (os2.us_weight_class as f32 - 50.0)) as GlyphUnit)
|
||||||
.font_file_2(id + 4),
|
.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>()?
|
.read_table::<CharMap>()?
|
||||||
.mapping
|
.mapping
|
||||||
.iter()
|
.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.
|
// Finally write the subsetted font program.
|
||||||
|
|
||||||
self.writer.write_obj(id + 4, &FontStream::new(font.data().get_ref()))?;
|
self.writer.write_obj(id + 4, &FontStream::new(font.data().get_ref()))?;
|
||||||
|
|
||||||
id += 5;
|
id += 5;
|
||||||
|
83
src/func.rs
83
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::{ParseContext, Parsed};
|
||||||
use crate::syntax::func::FuncHeader;
|
use crate::syntax::func::FuncHeader;
|
||||||
use crate::syntax::span::Spanned;
|
use crate::syntax::span::Spanned;
|
||||||
|
|
||||||
|
/// Types that are useful for creating your own functions.
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
pub use crate::{function, body, err};
|
||||||
pub use crate::layout::prelude::*;
|
pub use crate::layout::prelude::*;
|
||||||
pub use crate::layout::{LayoutContext, Commands, layout};
|
|
||||||
pub use crate::layout::Command::{self, *};
|
pub use crate::layout::Command::{self, *};
|
||||||
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
pub use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
pub use crate::syntax::SyntaxModel;
|
pub use crate::syntax::SyntaxModel;
|
||||||
pub use crate::syntax::expr::*;
|
pub use crate::syntax::expr::*;
|
||||||
pub use crate::syntax::func::*;
|
pub use crate::syntax::func::*;
|
||||||
pub use crate::syntax::func::keys::*;
|
|
||||||
pub use crate::syntax::func::values::*;
|
|
||||||
pub use crate::syntax::span::{Span, Spanned};
|
pub use crate::syntax::span::{Span, Spanned};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Parse a function from source code.
|
/// Parse a function from source code.
|
||||||
pub trait ParseFunc {
|
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;
|
type Meta: Clone;
|
||||||
|
|
||||||
/// Parse the header and body into this function given a context.
|
/// Parse the header and body into this function given a context.
|
||||||
@ -31,6 +39,49 @@ pub trait ParseFunc {
|
|||||||
) -> Parsed<Self> where Self: Sized;
|
) -> 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_export]
|
||||||
macro_rules! function {
|
macro_rules! function {
|
||||||
// Entry point.
|
// Entry point.
|
||||||
@ -118,8 +169,15 @@ macro_rules! function {
|
|||||||
|
|
||||||
/// Parse the body of a function.
|
/// Parse the body of a function.
|
||||||
///
|
///
|
||||||
/// - If the function does not expect a body, use `parse!(nope: body, errors)`.
|
/// - If the function does not expect a body, use `body!(nope: body, errors)`.
|
||||||
/// - If the function can have a body, use `parse!(opt: body, ctx, errors, decos)`.
|
/// - 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_export]
|
||||||
macro_rules! body {
|
macro_rules! body {
|
||||||
(opt: $body:expr, $ctx:expr, $errors:expr, $decos:expr) => ({
|
(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
|
/// # 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);
|
/// err!(span; "the wrong {}", value);
|
||||||
|
///
|
||||||
|
/// // With no span and severity `Warning`.
|
||||||
/// err!(@Warning: span; "non-fatal!");
|
/// err!(@Warning: span; "non-fatal!");
|
||||||
|
///
|
||||||
|
/// // Without span and default severity.
|
||||||
/// err!("no spans here ...");
|
/// err!("no spans here ...");
|
||||||
/// ```
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Drawing and cofiguration actions composing layouts.
|
//! Drawing and configuration actions composing layouts.
|
||||||
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
@ -9,14 +9,15 @@ use super::{Layout, Serialize};
|
|||||||
use self::LayoutAction::*;
|
use self::LayoutAction::*;
|
||||||
|
|
||||||
|
|
||||||
/// A layouting action.
|
/// A layouting action, which is the basic building block layouts are composed
|
||||||
|
/// of.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum LayoutAction {
|
pub enum LayoutAction {
|
||||||
/// Move to an absolute position.
|
/// Move to an absolute position.
|
||||||
MoveAbsolute(Size2D),
|
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),
|
SetFont(FontIndex, Size),
|
||||||
/// Write text starting at the current position.
|
/// Write text at the current position.
|
||||||
WriteText(String),
|
WriteText(String),
|
||||||
/// Visualize a box for debugging purposes.
|
/// Visualize a box for debugging purposes.
|
||||||
DebugBox(Size2D),
|
DebugBox(Size2D),
|
||||||
@ -50,17 +51,18 @@ debug_display!(LayoutAction);
|
|||||||
/// A sequence of layouting actions.
|
/// A sequence of layouting actions.
|
||||||
///
|
///
|
||||||
/// The sequence of actions is optimized as the actions are added. For example,
|
/// 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.
|
/// a font changing option will only be added if the selected font is not
|
||||||
/// All configuration actions (like moving, setting fonts, ...) are only flushed when
|
/// already active. All configuration actions (like moving, setting fonts, ...)
|
||||||
/// content is written.
|
/// are only flushed when content is written.
|
||||||
///
|
///
|
||||||
/// Furthermore, the action list can translate absolute position into a coordinate system
|
/// Furthermore, the action list can translate absolute position into a
|
||||||
/// with a different origin. This is realized in the `add_box` method, which allows a layout to
|
/// coordinate system with a different origin. This is realized in the
|
||||||
/// be added at a position, effectively translating all movement actions inside the layout
|
/// `add_layout` method, which allows a layout to be added at a position,
|
||||||
/// by the position.
|
/// effectively translating all movement actions inside the layout by the
|
||||||
|
/// position.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutActions {
|
pub struct LayoutActions {
|
||||||
pub origin: Size2D,
|
origin: Size2D,
|
||||||
actions: Vec<LayoutAction>,
|
actions: Vec<LayoutAction>,
|
||||||
active_font: (FontIndex, Size),
|
active_font: (FontIndex, Size),
|
||||||
next_pos: Option<Size2D>,
|
next_pos: Option<Size2D>,
|
||||||
@ -97,15 +99,14 @@ impl LayoutActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a series of actions.
|
/// Add a series of actions.
|
||||||
pub fn extend<I>(&mut self, actions: I)
|
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item = LayoutAction> {
|
||||||
where I: IntoIterator<Item = LayoutAction> {
|
|
||||||
for action in actions.into_iter() {
|
for action in actions.into_iter() {
|
||||||
self.add(action);
|
self.add(action);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a layout at a position. All move actions inside the layout are translated
|
/// Add a layout at a position. All move actions inside the layout are
|
||||||
/// by the position.
|
/// translated by the position.
|
||||||
pub fn add_layout(&mut self, position: Size2D, layout: Layout) {
|
pub fn add_layout(&mut self, position: Size2D, layout: Layout) {
|
||||||
self.flush_position();
|
self.flush_position();
|
||||||
|
|
||||||
@ -120,10 +121,9 @@ impl LayoutActions {
|
|||||||
self.actions.is_empty()
|
self.actions.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the list of actions as a vector, leaving an empty
|
/// Return the list of actions as a vector.
|
||||||
/// vector in its position.
|
pub fn into_vec(self) -> Vec<LayoutAction> {
|
||||||
pub fn to_vec(&mut self) -> Vec<LayoutAction> {
|
self.actions
|
||||||
std::mem::replace(&mut self.actions, vec![])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Append a cached move action if one is cached.
|
/// Append a cached move action if one is cached.
|
||||||
|
@ -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::stack::{StackLayouter, StackContext};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
/// The line layouter arranges boxes next to each other along a primary axis
|
/// Performs the line layouting.
|
||||||
/// and arranges the resulting lines using an underlying stack layouter.
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LineLayouter {
|
pub struct LineLayouter {
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
@ -34,7 +43,9 @@ pub struct LineContext {
|
|||||||
pub line_spacing: Size,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
struct LineRun {
|
struct LineRun {
|
||||||
/// The so-far accumulated layouts in the line.
|
/// The so-far accumulated layouts in the line.
|
||||||
@ -43,9 +54,13 @@ struct LineRun {
|
|||||||
/// line.
|
/// line.
|
||||||
size: Size2D,
|
size: Size2D,
|
||||||
/// The alignment of all layouts in the line.
|
/// 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>,
|
alignment: Option<LayoutAlignment>,
|
||||||
/// The remaining usable space if another differently aligned line run
|
/// If another line run with different alignment already took up some space
|
||||||
/// already took up some space.
|
/// of the line, this run has less space and how much is stored here.
|
||||||
usable: Option<Size>,
|
usable: Option<Size>,
|
||||||
/// A possibly cached soft spacing or spacing state.
|
/// A possibly cached soft spacing or spacing state.
|
||||||
last_spacing: LastSpacing,
|
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 {
|
fn usable(&self) -> Size2D {
|
||||||
// The base is the usable space per stack layouter.
|
// The base is the usable space per stack layouter.
|
||||||
let mut usable = self.stack.usable().generalized(self.ctx.axes);
|
let mut usable = self.stack.usable().generalized(self.ctx.axes);
|
||||||
@ -152,7 +170,7 @@ impl LineLayouter {
|
|||||||
usable
|
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) {
|
pub fn add_primary_spacing(&mut self, mut spacing: Size, kind: SpacingKind) {
|
||||||
match kind {
|
match kind {
|
||||||
// A hard space is simply an empty box.
|
// 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) {
|
pub fn add_secondary_spacing(&mut self, spacing: Size, kind: SpacingKind) {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.stack.add_spacing(spacing, kind)
|
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) {
|
pub fn set_axes(&mut self, axes: LayoutAxes) {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.ctx.axes = axes;
|
self.ctx.axes = axes;
|
||||||
self.stack.set_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
|
/// 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
|
/// 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());
|
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) {
|
pub fn set_line_spacing(&mut self, line_spacing: Size) {
|
||||||
self.ctx.line_spacing = line_spacing;
|
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 {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
let mut spaces = self.stack.remaining();
|
let mut spaces = self.stack.remaining();
|
||||||
*spaces[0].dimensions.get_secondary_mut(self.ctx.axes)
|
*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()
|
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 {
|
pub fn finish(mut self) -> MultiLayout {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.stack.finish()
|
self.stack.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finish the currently active space and start a new one.
|
/// 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) {
|
pub fn finish_space(&mut self, hard: bool) {
|
||||||
self.finish_line_if_not_empty();
|
self.finish_line_if_not_empty();
|
||||||
self.stack.finish_space(hard)
|
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) {
|
pub fn finish_line(&mut self) {
|
||||||
let mut actions = LayoutActions::new();
|
let mut actions = LayoutActions::new();
|
||||||
|
|
||||||
@ -251,7 +273,7 @@ impl LineLayouter {
|
|||||||
dimensions: self.run.size.specialized(self.ctx.axes),
|
dimensions: self.run.size.specialized(self.ctx.axes),
|
||||||
alignment: self.run.alignment
|
alignment: self.run.alignment
|
||||||
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
|
.unwrap_or(LayoutAlignment::new(Origin, Origin)),
|
||||||
actions: actions.to_vec(),
|
actions: actions.into_vec(),
|
||||||
});
|
});
|
||||||
|
|
||||||
self.run = LineRun::new();
|
self.run = LineRun::new();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! The core layouting engine.
|
//! Layouting types and engines.
|
||||||
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
@ -6,7 +6,7 @@ use smallvec::SmallVec;
|
|||||||
use toddle::query::FontIndex;
|
use toddle::query::FontIndex;
|
||||||
|
|
||||||
use crate::size::{Size, Size2D, SizeBox};
|
use crate::size::{Size, Size2D, SizeBox};
|
||||||
use self::{GenericAxis::*, SpecificAxis::*, Direction::*, Alignment::*};
|
use self::prelude::*;
|
||||||
|
|
||||||
pub mod line;
|
pub mod line;
|
||||||
pub mod stack;
|
pub mod stack;
|
||||||
@ -15,8 +15,13 @@ pub mod text;
|
|||||||
pub_use_mod!(actions);
|
pub_use_mod!(actions);
|
||||||
pub_use_mod!(model);
|
pub_use_mod!(model);
|
||||||
|
|
||||||
|
/// Basic types used across the layouting engine.
|
||||||
pub mod prelude {
|
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::GenericAxis::{self, *};
|
||||||
pub use super::SpecificAxis::{self, *};
|
pub use super::SpecificAxis::{self, *};
|
||||||
pub use super::Direction::{self, *};
|
pub use super::Direction::{self, *};
|
||||||
@ -27,7 +32,7 @@ pub mod prelude {
|
|||||||
/// A collection of layouts.
|
/// A collection of layouts.
|
||||||
pub type MultiLayout = Vec<Layout>;
|
pub type MultiLayout = Vec<Layout>;
|
||||||
|
|
||||||
/// A sequence of layouting actions inside a box.
|
/// A finished box with content at fixed positions.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Layout {
|
pub struct Layout {
|
||||||
/// The size of the box.
|
/// 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]>;
|
pub type LayoutSpaces = SmallVec<[LayoutSpace; 2]>;
|
||||||
|
|
||||||
/// Spacial layouting constraints.
|
/// The space into which content is laid out.
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct LayoutSpace {
|
pub struct LayoutSpace {
|
||||||
/// The maximum size of the box to layout in.
|
/// The maximum size of the box to layout in.
|
||||||
@ -92,8 +98,7 @@ pub struct LayoutSpace {
|
|||||||
/// Padding that should be respected on each side.
|
/// Padding that should be respected on each side.
|
||||||
pub padding: SizeBox,
|
pub padding: SizeBox,
|
||||||
/// Whether to expand the dimensions of the resulting layout to the full
|
/// 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
|
/// dimensions of this space or to shrink them to fit the content.
|
||||||
/// horizontal and vertical axis.
|
|
||||||
pub expansion: LayoutExpansion,
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct LayoutExpansion {
|
pub enum GenericAxis {
|
||||||
pub horizontal: bool,
|
/// The primary axis along which words are laid out.
|
||||||
pub vertical: bool,
|
Primary,
|
||||||
|
/// The secondary axis along which lines and paragraphs are laid out.
|
||||||
|
Secondary,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutExpansion {
|
impl GenericAxis {
|
||||||
pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion {
|
/// The specific version of this axis in the given system of axes.
|
||||||
LayoutExpansion { horizontal, vertical }
|
pub fn to_specific(self, axes: LayoutAxes) -> SpecificAxis {
|
||||||
|
axes.get(self).axis()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow the specified component mutably.
|
impl Display for GenericAxis {
|
||||||
pub fn get_mut(&mut self, axis: SpecificAxis) -> &mut bool {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
match axis {
|
match self {
|
||||||
Horizontal => &mut self.horizontal,
|
Primary => write!(f, "primary"),
|
||||||
Vertical => &mut self.vertical,
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct LayoutAxes {
|
pub struct LayoutAxes {
|
||||||
|
/// The primary layouting direction.
|
||||||
pub primary: Direction,
|
pub primary: Direction,
|
||||||
|
/// The secondary layouting direction.
|
||||||
pub secondary: Direction,
|
pub secondary: Direction,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAxes {
|
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 {
|
pub fn new(primary: Direction, secondary: Direction) -> LayoutAxes {
|
||||||
if primary.axis() == secondary.axis() {
|
if primary.axis() == secondary.axis() {
|
||||||
panic!("LayoutAxes::new: invalid aligned axes {:?} and {:?}",
|
panic!("LayoutAxes::new: invalid aligned axes \
|
||||||
primary, secondary);
|
{} and {}", primary, secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutAxes { primary, secondary }
|
LayoutAxes { primary, secondary }
|
||||||
@ -172,77 +213,11 @@ impl LayoutAxes {
|
|||||||
Secondary => &mut self.secondary,
|
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.
|
/// Directions along which content is laid out.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
LeftToRight,
|
LeftToRight,
|
||||||
RightToLeft,
|
RightToLeft,
|
||||||
@ -260,6 +235,8 @@ impl Direction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this axis points into the positive coordinate 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 {
|
pub fn is_positive(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
LeftToRight | TopToBottom => true,
|
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.
|
/// The inverse axis.
|
||||||
pub fn inv(self) -> Direction {
|
pub fn inv(self) -> Direction {
|
||||||
match self {
|
match self {
|
||||||
@ -276,14 +261,6 @@ impl Direction {
|
|||||||
BottomToTop => TopToBottom,
|
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 {
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct LayoutAlignment {
|
pub struct LayoutAlignment {
|
||||||
|
/// The alignment along the primary axis.
|
||||||
pub primary: Alignment,
|
pub primary: Alignment,
|
||||||
|
/// The alignment along the secondary axis.
|
||||||
pub secondary: Alignment,
|
pub secondary: Alignment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutAlignment {
|
impl LayoutAlignment {
|
||||||
|
/// Create a new instance from the two values.
|
||||||
pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment {
|
pub fn new(primary: Alignment, secondary: Alignment) -> LayoutAlignment {
|
||||||
LayoutAlignment { primary, secondary }
|
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.
|
/// Borrow the alignment of the specified generic axis mutably.
|
||||||
pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment {
|
pub fn get_mut(&mut self, axis: GenericAxis) -> &mut Alignment {
|
||||||
match axis {
|
match axis {
|
||||||
@ -321,8 +309,11 @@ impl LayoutAlignment {
|
|||||||
/// Where to align content.
|
/// Where to align content.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum Alignment {
|
pub enum Alignment {
|
||||||
|
/// Align content at the start of the axis.
|
||||||
Origin,
|
Origin,
|
||||||
|
/// Align content centered on the axis.
|
||||||
Center,
|
Center,
|
||||||
|
/// Align content at the end of the axis.
|
||||||
End,
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
pub enum SpacingKind {
|
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,
|
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),
|
Soft(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -357,11 +389,16 @@ impl SpacingKind {
|
|||||||
pub const WORD: SpacingKind = SpacingKind::Soft(1);
|
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)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
enum LastSpacing {
|
enum LastSpacing {
|
||||||
|
/// The last item was hard spacing.
|
||||||
Hard,
|
Hard,
|
||||||
|
/// The last item was soft spacing with the given width and level.
|
||||||
Soft(Size, u32),
|
Soft(Size, u32),
|
||||||
|
/// The last item was not spacing.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
@ -13,7 +17,7 @@ use super::text::{layout_text, TextContext};
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
/// Performs the model layouting.
|
||||||
pub struct ModelLayouter<'a, 'p> {
|
pub struct ModelLayouter<'a, 'p> {
|
||||||
ctx: LayoutContext<'a, 'p>,
|
ctx: LayoutContext<'a, 'p>,
|
||||||
layouter: LineLayouter,
|
layouter: LineLayouter,
|
||||||
@ -21,7 +25,7 @@ pub struct ModelLayouter<'a, 'p> {
|
|||||||
errors: Errors,
|
errors: Errors,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The general context for layouting.
|
/// The context for layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutContext<'a, 'p> {
|
pub struct LayoutContext<'a, 'p> {
|
||||||
/// The font loader to retrieve fonts from when typesetting text
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
@ -46,53 +50,74 @@ pub struct LayoutContext<'a, 'p> {
|
|||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of layouting: Some layouted things and a list of errors.
|
||||||
pub struct Layouted<T> {
|
pub struct Layouted<T> {
|
||||||
|
/// The result of the layouting process.
|
||||||
pub output: T,
|
pub output: T,
|
||||||
|
/// Errors that arose in the process of layouting.
|
||||||
pub errors: Errors,
|
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.
|
/// A sequence of layouting commands.
|
||||||
pub type Commands<'a> = Vec<Command<'a>>;
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum Command<'a> {
|
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),
|
LayoutSyntaxModel(&'a SyntaxModel),
|
||||||
|
|
||||||
|
/// Add a already computed layout.
|
||||||
Add(Layout),
|
Add(Layout),
|
||||||
|
/// Add multiple layouts, one after another. This is equivalent to multiple
|
||||||
|
/// [Add](Command::Add) commands.
|
||||||
AddMultiple(MultiLayout),
|
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),
|
AddSpacing(Size, SpacingKind, GenericAxis),
|
||||||
|
|
||||||
FinishLine,
|
/// Start a new line.
|
||||||
FinishSpace,
|
BreakLine,
|
||||||
|
/// Start a new paragraph.
|
||||||
BreakParagraph,
|
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,
|
BreakPage,
|
||||||
|
|
||||||
|
/// Update the text style.
|
||||||
SetTextStyle(TextStyle),
|
SetTextStyle(TextStyle),
|
||||||
|
/// Update the page style.
|
||||||
SetPageStyle(PageStyle),
|
SetPageStyle(PageStyle),
|
||||||
|
|
||||||
|
/// Update the alignment for future boxes added to this layouting process.
|
||||||
SetAlignment(LayoutAlignment),
|
SetAlignment(LayoutAlignment),
|
||||||
|
/// Update the layouting axes along which future boxes will be laid out.
|
||||||
|
/// This finishes the current line.
|
||||||
SetAxes(LayoutAxes),
|
SetAxes(LayoutAxes),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Layout a syntax model into a list of boxes.
|
||||||
pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted<MultiLayout> {
|
pub async fn layout(model: &SyntaxModel, ctx: LayoutContext<'_, '_>) -> Layouted<MultiLayout> {
|
||||||
let mut layouter = ModelLayouter::new(ctx);
|
let mut layouter = ModelLayouter::new(ctx);
|
||||||
layouter.layout_syntax_model(model).await;
|
layouter.layout_syntax_model(model).await;
|
||||||
layouter.finish()
|
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>>;
|
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>;
|
||||||
|
|
||||||
impl<'a, 'p> ModelLayouter<'a, 'p> {
|
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> {
|
pub fn new(ctx: LayoutContext<'a, 'p>) -> ModelLayouter<'a, 'p> {
|
||||||
ModelLayouter {
|
ModelLayouter {
|
||||||
layouter: LineLayouter::new(LineContext {
|
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>(
|
pub fn layout<'r>(
|
||||||
&'r mut self,
|
&'r mut self,
|
||||||
model: Spanned<&'r dyn Model>
|
model: Spanned<&'r dyn Model>
|
||||||
) -> DynFuture<'r, ()> { Box::pin(async move {
|
) -> DynFuture<'r, ()> { Box::pin(async move {
|
||||||
|
// Execute the model's layout function which generates the commands.
|
||||||
let layouted = model.v.layout(LayoutContext {
|
let layouted = model.v.layout(LayoutContext {
|
||||||
style: &self.style,
|
style: &self.style,
|
||||||
spaces: self.layouter.remaining(),
|
spaces: self.layouter.remaining(),
|
||||||
@ -121,14 +148,16 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
|
|||||||
.. self.ctx
|
.. self.ctx
|
||||||
}).await;
|
}).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));
|
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;
|
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>(
|
pub fn layout_syntax_model<'r>(
|
||||||
&'r mut self,
|
&'r mut self,
|
||||||
model: &'r SyntaxModel
|
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> {
|
pub fn finish(self) -> Layouted<MultiLayout> {
|
||||||
Layouted {
|
Layouted {
|
||||||
output: self.layouter.finish(),
|
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>(
|
fn execute_command<'r>(
|
||||||
&'r mut self,
|
&'r mut self,
|
||||||
command: Command<'r>,
|
command: Command<'r>,
|
||||||
@ -186,8 +218,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
|
|||||||
Secondary => self.layouter.add_secondary_spacing(space, kind),
|
Secondary => self.layouter.add_secondary_spacing(space, kind),
|
||||||
}
|
}
|
||||||
|
|
||||||
FinishLine => self.layouter.finish_line(),
|
BreakLine => self.layouter.finish_line(),
|
||||||
FinishSpace => self.layouter.finish_space(true),
|
|
||||||
BreakParagraph => self.layout_paragraph(),
|
BreakParagraph => self.layout_paragraph(),
|
||||||
BreakPage => {
|
BreakPage => {
|
||||||
if self.ctx.nested {
|
if self.ctx.nested {
|
||||||
@ -209,6 +240,9 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
|
|||||||
} else {
|
} else {
|
||||||
self.style.page = style;
|
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();
|
let margins = style.margins();
|
||||||
self.ctx.base = style.dimensions.unpadded(margins);
|
self.ctx.base = style.dimensions.unpadded(margins);
|
||||||
self.layouter.set_spaces(smallvec![
|
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) {
|
async fn layout_text(&mut self, text: &str) {
|
||||||
self.layouter.add(layout_text(text, TextContext {
|
self.layouter.add(layout_text(text, TextContext {
|
||||||
loader: &self.ctx.loader,
|
loader: &self.ctx.loader,
|
||||||
@ -238,6 +273,7 @@ impl<'a, 'p> ModelLayouter<'a, 'p> {
|
|||||||
}).await)
|
}).await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add the spacing for a syntactic space node.
|
||||||
fn layout_space(&mut self) {
|
fn layout_space(&mut self) {
|
||||||
self.layouter.add_primary_spacing(
|
self.layouter.add_primary_spacing(
|
||||||
self.style.text.word_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) {
|
fn layout_paragraph(&mut self) {
|
||||||
self.layouter.add_secondary_spacing(
|
self.layouter.add_secondary_spacing(
|
||||||
self.style.text.paragraph_spacing(),
|
self.style.text.paragraph_spacing(),
|
||||||
|
@ -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 smallvec::smallvec;
|
||||||
use crate::size::ValueBox;
|
use crate::size::ValueBox;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
/// The stack layouter stack boxes onto each other along the secondary layouting
|
/// Performs the stack layouting.
|
||||||
/// axis.
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StackLayouter {
|
pub struct StackLayouter {
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
@ -222,8 +244,8 @@ impl StackLayouter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The remaining unpadded, unexpanding spaces. If a multi-layout is laid
|
/// The remaining unpadded, unexpanding spaces. If a function is laid out
|
||||||
/// out into these spaces, it will fit into this stack.
|
/// into these spaces, it will fit into this stack.
|
||||||
pub fn remaining(&self) -> LayoutSpaces {
|
pub fn remaining(&self) -> LayoutSpaces {
|
||||||
let dimensions = self.usable();
|
let dimensions = self.usable();
|
||||||
|
|
||||||
@ -257,7 +279,7 @@ impl StackLayouter {
|
|||||||
self.space.index == self.ctx.spaces.len() - 1
|
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 {
|
pub fn finish(mut self) -> MultiLayout {
|
||||||
if self.space.hard || !self.space_is_empty() {
|
if self.space.hard || !self.space_is_empty() {
|
||||||
self.finish_space(false);
|
self.finish_space(false);
|
||||||
@ -373,7 +395,7 @@ impl StackLayouter {
|
|||||||
self.layouts.push(Layout {
|
self.layouts.push(Layout {
|
||||||
dimensions,
|
dimensions,
|
||||||
alignment: self.ctx.alignment,
|
alignment: self.ctx.alignment,
|
||||||
actions: actions.to_vec(),
|
actions: actions.into_vec(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ------------------------------------------------------------------ //
|
// ------------------------------------------------------------------ //
|
||||||
|
@ -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::query::{SharedFontLoader, FontQuery, FontIndex};
|
||||||
use toddle::tables::{CharMap, Header, HorizontalMetrics};
|
use toddle::tables::{CharMap, Header, HorizontalMetrics};
|
||||||
|
|
||||||
@ -6,7 +12,7 @@ use crate::style::TextStyle;
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
/// Layouts text into boxes.
|
/// Performs the text layouting.
|
||||||
struct TextLayouter<'a, 'p> {
|
struct TextLayouter<'a, 'p> {
|
||||||
ctx: TextContext<'a, 'p>,
|
ctx: TextContext<'a, 'p>,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
@ -17,20 +23,22 @@ struct TextLayouter<'a, 'p> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The context for text layouting.
|
/// The context for text layouting.
|
||||||
///
|
|
||||||
/// See [`LayoutContext`] for details about the fields.
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct TextContext<'a, 'p> {
|
pub struct TextContext<'a, 'p> {
|
||||||
|
/// The font loader to retrieve fonts from when typesetting text
|
||||||
|
/// using [`layout_text`].
|
||||||
pub loader: &'a SharedFontLoader<'p>,
|
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,
|
pub style: &'a TextStyle,
|
||||||
|
/// The axes along which the word is laid out. For now, only
|
||||||
|
/// primary-horizontal layouting is supported.
|
||||||
pub axes: LayoutAxes,
|
pub axes: LayoutAxes,
|
||||||
|
/// The alignment of the finished layout.
|
||||||
pub alignment: LayoutAlignment,
|
pub alignment: LayoutAlignment,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layouts text into a box.
|
/// 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 {
|
pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> Layout {
|
||||||
TextLayouter::new(text, ctx).layout().await
|
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 {
|
async fn layout(mut self) -> Layout {
|
||||||
|
// If the primary axis is negative, we layout the characters reversed.
|
||||||
if self.ctx.axes.primary.is_positive() {
|
if self.ctx.axes.primary.is_positive() {
|
||||||
for c in self.text.chars() {
|
for c in self.text.chars() {
|
||||||
self.layout_char(c).await;
|
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() {
|
if !self.buffer.is_empty() {
|
||||||
self.actions.add(LayoutAction::WriteText(self.buffer));
|
self.actions.add(LayoutAction::WriteText(self.buffer));
|
||||||
}
|
}
|
||||||
@ -67,7 +77,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
|
|||||||
Layout {
|
Layout {
|
||||||
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
|
dimensions: Size2D::new(self.width, self.ctx.style.font_size()),
|
||||||
alignment: self.ctx.alignment,
|
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;
|
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.active_font != index {
|
||||||
if !self.buffer.is_empty() {
|
if !self.buffer.is_empty() {
|
||||||
let text = std::mem::replace(&mut self.buffer, String::new());
|
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 {
|
if let Some((font, index)) = loader.get(query).await {
|
||||||
|
// Determine the width of the char.
|
||||||
let header = font.read_table::<Header>().ok()?;
|
let header = font.read_table::<Header>().ok()?;
|
||||||
let font_unit_ratio = 1.0 / (header.units_per_em as f32);
|
let font_unit_ratio = 1.0 / (header.units_per_em as f32);
|
||||||
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
|
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
|
||||||
|
21
src/lib.rs
21
src/lib.rs
@ -7,27 +7,24 @@
|
|||||||
//! be found in the [syntax](crate::syntax) module.
|
//! be found in the [syntax](crate::syntax) module.
|
||||||
//! - **Layouting:** The next step is to transform the syntax tree into a
|
//! - **Layouting:** The next step is to transform the syntax tree into a
|
||||||
//! portable representation of the typesetted document. Types for these can be
|
//! portable representation of the typesetted document. Types for these can be
|
||||||
//! found in the [layout] module. A finished layout reading for exporting is a
|
//! found in the [layout](crate::layout) module. A finished layout reading for
|
||||||
//! [multi-layout](crate::layout::MultiLayout) consisting of multiple boxes
|
//! exporting is a [MultiLayout](crate::layout::MultiLayout) consisting of
|
||||||
//! (or pages).
|
//! multiple boxes (or pages).
|
||||||
//! - **Exporting:** The finished layout can then be exported into a supported
|
//! - **Exporting:** The finished layout can then be exported into a supported
|
||||||
//! format. Submodules for these formats are located in the
|
//! format. Submodules for these formats are located in the
|
||||||
//! [export](crate::export) module. Currently, the only supported output
|
//! [export](crate::export) module. Currently, the only supported output
|
||||||
//! format is _PDF_. Alternatively, the layout can be serialized to pass it to
|
//! format is [_PDF_](crate::export::pdf). Alternatively, the layout can be
|
||||||
//! a suitable renderer.
|
//! serialized to pass it to a suitable renderer.
|
||||||
|
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
pub extern crate toddle;
|
pub use toddle;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
|
|
||||||
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
||||||
|
|
||||||
use crate::layout::MultiLayout;
|
use crate::layout::{Layouted, MultiLayout};
|
||||||
use crate::layout::prelude::*;
|
|
||||||
use crate::layout::{LayoutContext, Layouted, layout};
|
|
||||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||||
use crate::syntax::{SyntaxModel, Scope, ParseContext, Parsed, parse};
|
use crate::syntax::{SyntaxModel, Scope, ParseContext, Parsed, parse};
|
||||||
use crate::syntax::span::Position;
|
use crate::syntax::span::Position;
|
||||||
@ -95,8 +92,10 @@ impl<'p> Typesetter<'p> {
|
|||||||
|
|
||||||
/// Layout a syntax tree and return the produced layout.
|
/// Layout a syntax tree and return the produced layout.
|
||||||
pub async fn layout(&self, model: &SyntaxModel) -> Layouted<MultiLayout> {
|
pub async fn layout(&self, model: &SyntaxModel) -> Layouted<MultiLayout> {
|
||||||
|
use crate::layout::prelude::*;
|
||||||
|
|
||||||
let margins = self.style.page.margins();
|
let margins = self.style.page.margins();
|
||||||
layout(
|
crate::layout::layout(
|
||||||
&model,
|
&model,
|
||||||
LayoutContext {
|
LayoutContext {
|
||||||
loader: &self.loader,
|
loader: &self.loader,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use crate::size::PSize;
|
use crate::size::PSize;
|
||||||
use crate::syntax::func::maps::{AxisMap, PosAxisMap};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
@ -21,7 +20,10 @@ function! {
|
|||||||
layout(self, ctx, errors) {
|
layout(self, ctx, errors) {
|
||||||
ctx.base = ctx.spaces[0].dimensions;
|
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] {
|
for &axis in &[Primary, Secondary] {
|
||||||
if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) {
|
if let Some(Spanned { v: alignment, span }) = map.get_spanned(axis) {
|
||||||
if let Some(generic) = alignment.to_generic(ctx.axes, axis) {
|
if let Some(generic) = alignment.to_generic(ctx.axes, axis) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! The standard library.
|
//! The _Typst_ standard library.
|
||||||
|
|
||||||
use crate::syntax::Scope;
|
use crate::syntax::Scope;
|
||||||
use crate::func::prelude::*;
|
use crate::func::prelude::*;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::size::Size;
|
use crate::size::Size;
|
||||||
use crate::style::{Paper, PaperClass};
|
use crate::style::{Paper, PaperClass};
|
||||||
use crate::syntax::func::maps::{AxisMap, PaddingMap};
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ function! {
|
|||||||
pub struct LineBreakFunc;
|
pub struct LineBreakFunc;
|
||||||
|
|
||||||
parse(default)
|
parse(default)
|
||||||
layout(self, ctx, errors) { vec![FinishLine] }
|
layout(self, ctx, errors) { vec![BreakLine] }
|
||||||
}
|
}
|
||||||
|
|
||||||
function! {
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum ContentKind {
|
pub enum ContentKind {
|
||||||
Word,
|
Word,
|
||||||
Line,
|
Line,
|
||||||
|
@ -95,6 +95,7 @@ impl Sum for Size {
|
|||||||
|
|
||||||
/// Either an absolute size or a factor of some entity.
|
/// Either an absolute size or a factor of some entity.
|
||||||
#[derive(Copy, Clone, PartialEq)]
|
#[derive(Copy, Clone, PartialEq)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum ScaleSize {
|
pub enum ScaleSize {
|
||||||
Absolute(Size),
|
Absolute(Size),
|
||||||
Scaled(f32),
|
Scaled(f32),
|
||||||
|
33
src/style.rs
33
src/style.rs
@ -7,7 +7,9 @@ use crate::size::{Size, Size2D, SizeBox, ValueBox, PSize};
|
|||||||
/// Defines properties of pages and text.
|
/// Defines properties of pages and text.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct LayoutStyle {
|
pub struct LayoutStyle {
|
||||||
|
/// The style for pages.
|
||||||
pub page: PageStyle,
|
pub page: PageStyle,
|
||||||
|
/// The style for text.
|
||||||
pub text: TextStyle,
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum PaperClass {
|
pub enum PaperClass {
|
||||||
Custom,
|
Custom,
|
||||||
Base,
|
Base,
|
||||||
@ -185,24 +188,28 @@ impl PaperClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! papers {
|
macro_rules! papers {
|
||||||
($(($var:ident: $class:expr, $width:expr, $height: expr, $($patterns:tt)*))*) => {
|
($(($var:ident: $class:ident, $width:expr, $height: expr, $($pats:tt)*))*) => {
|
||||||
use PaperClass::*;
|
$(papers!(@$var, stringify!($($pats)*), $class, $width, $height);)*
|
||||||
|
|
||||||
$(/// The infos for the paper that's in the name.
|
fn parse_paper(paper: &str) -> Option<Paper> {
|
||||||
|
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 {
|
pub const $var: Paper = Paper {
|
||||||
dimensions: Size2D {
|
dimensions: Size2D {
|
||||||
x: Size { points: 2.83465 * $width },
|
x: Size { points: 2.83465 * $width },
|
||||||
y: Size { points: 2.83465 * $height },
|
y: Size { points: 2.83465 * $height },
|
||||||
},
|
},
|
||||||
class: $class,
|
class: PaperClass::$class,
|
||||||
};)*
|
};
|
||||||
|
|
||||||
fn parse_paper(paper: &str) -> Option<Paper> {
|
|
||||||
match paper.to_lowercase().as_str() {
|
|
||||||
$($($patterns)* => Some($var),)*
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
//! Expressions in function headers.
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
|
|
||||||
use crate::error::Errors;
|
use crate::error::Errors;
|
||||||
use crate::size::Size;
|
use crate::size::Size;
|
||||||
use super::func::{keys::Key, values::Value};
|
use super::func::{Key, Value};
|
||||||
use super::span::{Span, Spanned};
|
use super::span::{Span, Spanned};
|
||||||
use super::tokens::is_identifier;
|
use super::tokens::is_identifier;
|
||||||
|
|
||||||
@ -10,16 +12,24 @@ use super::tokens::is_identifier;
|
|||||||
/// An argument or return value.
|
/// An argument or return value.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum Expr {
|
pub enum Expr {
|
||||||
|
/// An identifier: `ident`.
|
||||||
Ident(Ident),
|
Ident(Ident),
|
||||||
|
/// A string: `"string"`.
|
||||||
Str(String),
|
Str(String),
|
||||||
|
/// A number: `1.2, 200%`.
|
||||||
Number(f64),
|
Number(f64),
|
||||||
|
/// A size: `2cm, 5.2in`.
|
||||||
Size(Size),
|
Size(Size),
|
||||||
|
/// A bool: `true, false`.
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
/// A tuple: `(false, 12cm, "hi")`.
|
||||||
Tuple(Tuple),
|
Tuple(Tuple),
|
||||||
|
/// An object: `{ fit: false, size: 12pt }`.
|
||||||
Object(Object),
|
Object(Object),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
impl Expr {
|
||||||
|
/// A natural-language name of the type of this expression, e.g. "identifier".
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
use Expr::*;
|
use Expr::*;
|
||||||
match self {
|
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)]
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct Ident(pub String);
|
pub struct Ident(pub String);
|
||||||
|
|
||||||
impl Ident {
|
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> {
|
pub fn new<S>(ident: S) -> Option<Ident> where S: AsRef<str> + Into<String> {
|
||||||
if is_identifier(ident.as_ref()) {
|
if is_identifier(ident.as_ref()) {
|
||||||
Some(Ident(ident.into()))
|
Some(Ident(ident.into()))
|
||||||
@ -47,26 +67,37 @@ impl Ident {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return a reference to the underlying string.
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
self.0.as_str()
|
self.0.as_str()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A sequence of expressions.
|
/// An untyped sequence of expressions.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```typst
|
||||||
|
/// (false, 12cm, "hi")
|
||||||
|
/// ```
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Tuple {
|
pub struct Tuple {
|
||||||
|
/// The elements of the tuple.
|
||||||
pub items: Vec<Spanned<Expr>>,
|
pub items: Vec<Spanned<Expr>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tuple {
|
impl Tuple {
|
||||||
|
/// Create an empty tuple.
|
||||||
pub fn new() -> Tuple {
|
pub fn new() -> Tuple {
|
||||||
Tuple { items: vec![] }
|
Tuple { items: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add an element.
|
||||||
pub fn add(&mut self, item: Spanned<Expr>) {
|
pub fn add(&mut self, item: Spanned<Expr>) {
|
||||||
self.items.push(item);
|
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> {
|
pub fn get<V: Value>(&mut self, errors: &mut Errors) -> Option<V::Output> {
|
||||||
while !self.items.is_empty() {
|
while !self.items.is_empty() {
|
||||||
let expr = self.items.remove(0);
|
let expr = self.items.remove(0);
|
||||||
@ -79,6 +110,8 @@ impl Tuple {
|
|||||||
None
|
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)
|
pub fn get_all<'a, V: Value>(&'a mut self, errors: &'a mut Errors)
|
||||||
-> impl Iterator<Item=V::Output> + 'a {
|
-> impl Iterator<Item=V::Output> + 'a {
|
||||||
self.items.drain(..).filter_map(move |expr| {
|
self.items.drain(..).filter_map(move |expr| {
|
||||||
@ -92,36 +125,63 @@ impl Tuple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A key-value collection of identifiers and associated expressions.
|
/// 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)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Object {
|
pub struct Object {
|
||||||
|
/// The key-value pairs of the object.
|
||||||
pub pairs: Vec<Pair>,
|
pub pairs: Vec<Pair>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A key-value pair in an object.
|
/// A key-value pair in an object.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Pair {
|
pub struct Pair {
|
||||||
|
/// The key part.
|
||||||
|
/// ```typst
|
||||||
|
/// key: value
|
||||||
|
/// ^^^
|
||||||
|
/// ```
|
||||||
pub key: Spanned<Ident>,
|
pub key: Spanned<Ident>,
|
||||||
|
/// The value part.
|
||||||
|
/// ```typst
|
||||||
|
/// key: value
|
||||||
|
/// ^^^^^
|
||||||
|
/// ```
|
||||||
pub value: Spanned<Expr>,
|
pub value: Spanned<Expr>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Object {
|
impl Object {
|
||||||
|
/// Create an empty object.
|
||||||
pub fn new() -> Object {
|
pub fn new() -> Object {
|
||||||
Object { pairs: vec![] }
|
Object { pairs: vec![] }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, key: Spanned<Ident>, value: Spanned<Expr>) {
|
/// Add a pair to object.
|
||||||
self.pairs.push(Pair { key, value });
|
pub fn add(&mut self, pair: Pair) {
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_pair(&mut self, pair: Pair) {
|
|
||||||
self.pairs.push(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> {
|
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)?;
|
let index = self.pairs.iter().position(|pair| pair.key.v.as_str() == key)?;
|
||||||
self.get_index::<V>(errors, index)
|
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>(
|
pub fn get_with_key<K: Key, V: Value>(
|
||||||
&mut self,
|
&mut self,
|
||||||
errors: &mut Errors,
|
errors: &mut Errors,
|
||||||
@ -135,6 +195,9 @@ impl Object {
|
|||||||
None
|
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>(
|
pub fn get_all<'a, K: Key, V: Value>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
errors: &'a mut Errors,
|
errors: &'a mut Errors,
|
||||||
@ -157,6 +220,13 @@ impl Object {
|
|||||||
}).filter_map(|x| x)
|
}).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>(
|
pub fn get_all_spanned<'a, K: Key + 'a, V: Value + 'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
errors: &'a mut Errors,
|
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)))
|
.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> {
|
fn get_index<V: Value>(&mut self, errors: &mut Errors, index: usize) -> Option<V::Output> {
|
||||||
let expr = self.pairs.remove(index).value;
|
let expr = self.pairs.remove(index).value;
|
||||||
let span = expr.span;
|
let span = expr.span;
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Key types for identifying keyword arguments.
|
||||||
|
|
||||||
use crate::layout::prelude::*;
|
use crate::layout::prelude::*;
|
||||||
use super::values::AlignmentValue::{self, *};
|
use super::values::AlignmentValue::{self, *};
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -6,10 +8,55 @@ use self::AxisKey::*;
|
|||||||
use self::PaddingKey::*;
|
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 {
|
pub trait Key {
|
||||||
|
/// The type to parse into.
|
||||||
type Output: Eq;
|
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>;
|
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 {
|
macro_rules! key {
|
||||||
($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => {
|
($type:ty, $output:ty, $($($p:pat)|* => $r:expr),* $(,)?) => {
|
||||||
impl Key for $type {
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum AxisKey {
|
pub enum AxisKey {
|
||||||
Generic(GenericAxis),
|
Generic(GenericAxis),
|
||||||
Specific(SpecificAxis),
|
Specific(SpecificAxis),
|
||||||
@ -68,6 +117,8 @@ key!(AxisKey, Self,
|
|||||||
"secondary" | "s" => Generic(Secondary),
|
"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;
|
pub struct ExtentKey;
|
||||||
|
|
||||||
key!(ExtentKey, AxisKey,
|
key!(ExtentKey, AxisKey,
|
||||||
@ -77,8 +128,13 @@ key!(ExtentKey, AxisKey,
|
|||||||
"secondary-size" | "ss" => Generic(Secondary),
|
"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.
|
/// 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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum PosAxisKey {
|
pub enum PosAxisKey {
|
||||||
/// The first positional argument.
|
/// The first positional argument.
|
||||||
|
@ -9,40 +9,46 @@ use super::values::*;
|
|||||||
use super::*;
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct DedupMap<K, V> where K: Eq {
|
pub struct DedupMap<K, V> where K: Eq {
|
||||||
map: Vec<Spanned<(K, V)>>,
|
map: Vec<Spanned<(K, V)>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K, V> DedupMap<K, V> where K: Eq {
|
impl<K, V> DedupMap<K, V> where K: Eq {
|
||||||
|
/// Create a new deduplicating map.
|
||||||
pub fn new() -> DedupMap<K, V> {
|
pub fn new() -> DedupMap<K, V> {
|
||||||
DedupMap { map: vec![] }
|
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>
|
pub fn from_iter<I>(errors: &mut Errors, iter: I) -> DedupMap<K, V>
|
||||||
where I: IntoIterator<Item=Spanned<(K, V)>> {
|
where I: IntoIterator<Item=Spanned<(K, V)>> {
|
||||||
let mut map = DedupMap::new();
|
let mut map = DedupMap::new();
|
||||||
for Spanned { v: (key, value), span } in iter.into_iter() {
|
map.extend(errors, iter);
|
||||||
map.insert(errors, key, value, span);
|
|
||||||
}
|
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a key-value pair.
|
/// Add a spanned key-value pair.
|
||||||
pub fn insert(&mut self, errors: &mut Errors, key: K, value: V, span: Span) {
|
pub fn insert(&mut self, errors: &mut Errors, entry: Spanned<(K, V)>) {
|
||||||
if self.map.iter().any(|e| e.v.0 == key) {
|
if self.map.iter().any(|e| e.v.0 == entry.v.0) {
|
||||||
errors.push(err!(span; "duplicate argument"));
|
errors.push(err!(entry.span; "duplicate argument"));
|
||||||
} else {
|
} 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)
|
pub fn extend<I>(&mut self, errors: &mut Errors, items: I)
|
||||||
where I: IntoIterator<Item=Spanned<(K, V)>> {
|
where I: IntoIterator<Item=Spanned<(K, V)>> {
|
||||||
for Spanned { v: (k, v), span } in items.into_iter() {
|
for item in items.into_iter() {
|
||||||
self.insert(errors, k, v, span);
|
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
|
/// Create a new map where keys and values are mapped to new keys and
|
||||||
/// values.
|
/// values. When the mapping introduces new duplicates, errors are
|
||||||
///
|
/// generated.
|
||||||
/// Returns an error if a new key is duplicate.
|
|
||||||
pub fn dedup<F, K2, V2>(&self, errors: &mut Errors, mut f: F) -> DedupMap<K2, V2>
|
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 {
|
where F: FnMut(&K, &V) -> (K2, V2), K2: Eq {
|
||||||
let mut map = DedupMap::new();
|
let mut map = DedupMap::new();
|
||||||
|
|
||||||
for Spanned { v: (key, value), span } in self.map.iter() {
|
for Spanned { v: (key, value), span } in self.map.iter() {
|
||||||
let (key, value) = f(key, value);
|
let (key, value) = f(key, value);
|
||||||
map.insert(errors, key, value, *span);
|
map.insert(errors, Spanned { v: (key, value), span: *span });
|
||||||
}
|
}
|
||||||
|
|
||||||
map
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct AxisMap<V>(DedupMap<AxisKey, V>);
|
pub struct AxisMap<V>(DedupMap<AxisKey, V>);
|
||||||
|
|
||||||
impl<V: Clone> AxisMap<V> {
|
impl<V: Clone> AxisMap<V> {
|
||||||
|
/// Parse an axis map from the object.
|
||||||
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
|
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
|
||||||
errors: &mut Errors,
|
errors: &mut Errors,
|
||||||
object: &mut Object,
|
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
|
/// A map for storing values for axes that are given through a combination of
|
||||||
/// positional or keyword arguments.
|
/// (two) positional and keyword arguments.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
|
pub struct PosAxisMap<V>(DedupMap<PosAxisKey, V>);
|
||||||
|
|
||||||
impl<V: Clone> PosAxisMap<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>>(
|
pub fn parse<KT: Key<Output=AxisKey>, VT: Value<Output=V>>(
|
||||||
errors: &mut Errors,
|
errors: &mut Errors,
|
||||||
args: &mut FuncArgs,
|
args: &mut FuncArgs,
|
||||||
@ -118,8 +125,8 @@ impl<V: Clone> PosAxisMap<V> {
|
|||||||
let mut map = DedupMap::new();
|
let mut map = DedupMap::new();
|
||||||
|
|
||||||
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
|
for &key in &[PosAxisKey::First, PosAxisKey::Second] {
|
||||||
if let Some(value) = args.pos.get::<Spanned<VT>>(errors) {
|
if let Some(Spanned { v, span }) = args.pos.get::<Spanned<VT>>(errors) {
|
||||||
map.insert(errors, key, value.v, value.span);
|
map.insert(errors, Spanned { v: (key, v), span })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +140,8 @@ impl<V: Clone> PosAxisMap<V> {
|
|||||||
PosAxisMap(map)
|
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>(
|
pub fn dedup<F>(
|
||||||
&self,
|
&self,
|
||||||
errors: &mut Errors,
|
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
|
/// A map for storing padding given for a combination of all sides, opposing
|
||||||
/// sides, opposing sides or single sides.
|
/// sides or single sides.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<PSize>>);
|
pub struct PaddingMap(DedupMap<PaddingKey<AxisKey>, Option<PSize>>);
|
||||||
|
|
||||||
impl PaddingMap {
|
impl PaddingMap {
|
||||||
|
/// Parse a padding map from the function arguments.
|
||||||
pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap {
|
pub fn parse(errors: &mut Errors, args: &mut FuncArgs) -> PaddingMap {
|
||||||
let mut map = DedupMap::new();
|
let mut map = DedupMap::new();
|
||||||
|
|
||||||
if let Some(psize) = args.pos.get::<Spanned<Defaultable<PSize>>>(errors) {
|
let all = args.pos.get::<Spanned<Defaultable<PSize>>>(errors);
|
||||||
map.insert(errors, PaddingKey::All, psize.v, psize.span);
|
if let Some(Spanned { v, span }) = all {
|
||||||
|
map.insert(errors, Spanned { v: (PaddingKey::All, v), span });
|
||||||
}
|
}
|
||||||
|
|
||||||
let paddings: Vec<_> = args.key
|
let paddings: Vec<_> = args.key
|
||||||
@ -187,8 +197,9 @@ impl PaddingMap {
|
|||||||
All => All,
|
All => All,
|
||||||
Both(axis) => Both(axis.to_specific(axes)),
|
Both(axis) => Both(axis.to_specific(axes)),
|
||||||
Side(axis, alignment) => {
|
Side(axis, alignment) => {
|
||||||
|
let generic = axis.to_generic(axes);
|
||||||
let axis = axis.to_specific(axes);
|
let axis = axis.to_specific(axes);
|
||||||
Side(axis, alignment.to_specific(axes, axis))
|
Side(axis, alignment.to_specific(axes, generic))
|
||||||
}
|
}
|
||||||
}, val)
|
}, val)
|
||||||
});
|
});
|
||||||
|
@ -1,25 +1,38 @@
|
|||||||
|
//! Primitives for argument parsing in library functions.
|
||||||
|
|
||||||
use crate::error::{Error, Errors};
|
use crate::error::{Error, Errors};
|
||||||
use super::expr::{Expr, Ident, Tuple, Object, Pair};
|
use super::expr::{Expr, Ident, Tuple, Object, Pair};
|
||||||
use super::span::{Span, Spanned};
|
use super::span::{Span, Spanned};
|
||||||
|
|
||||||
pub mod maps;
|
pub_use_mod!(maps);
|
||||||
pub mod keys;
|
pub_use_mod!(keys);
|
||||||
pub mod values;
|
pub_use_mod!(values);
|
||||||
|
|
||||||
|
|
||||||
|
/// The parsed header of a function.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncHeader {
|
pub struct FuncHeader {
|
||||||
|
/// The function name, that is:
|
||||||
|
/// ```typst
|
||||||
|
/// [box: w=5cm]
|
||||||
|
/// ^^^
|
||||||
|
/// ```
|
||||||
pub name: Spanned<Ident>,
|
pub name: Spanned<Ident>,
|
||||||
|
/// The arguments passed to the function.
|
||||||
pub args: FuncArgs,
|
pub args: FuncArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The positional and keyword arguments passed to a function.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct FuncArgs {
|
pub struct FuncArgs {
|
||||||
|
/// The positional arguments.
|
||||||
pub pos: Tuple,
|
pub pos: Tuple,
|
||||||
|
/// They keyword arguments.
|
||||||
pub key: Object,
|
pub key: Object,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncArgs {
|
impl FuncArgs {
|
||||||
|
/// Create new empty function arguments.
|
||||||
pub fn new() -> FuncArgs {
|
pub fn new() -> FuncArgs {
|
||||||
FuncArgs {
|
FuncArgs {
|
||||||
pos: Tuple::new(),
|
pos: Tuple::new(),
|
||||||
@ -30,40 +43,32 @@ impl FuncArgs {
|
|||||||
/// Add an argument.
|
/// Add an argument.
|
||||||
pub fn add(&mut self, arg: FuncArg) {
|
pub fn add(&mut self, arg: FuncArg) {
|
||||||
match arg {
|
match arg {
|
||||||
FuncArg::Pos(item) => self.add_pos(item),
|
FuncArg::Pos(item) => self.pos.add(item),
|
||||||
FuncArg::Key(pair) => self.add_key_pair(pair),
|
FuncArg::Key(pair) => self.key.add(pair),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a positional argument.
|
/// Iterate over all arguments.
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_iter(self) -> impl Iterator<Item=FuncArg> {
|
pub fn into_iter(self) -> impl Iterator<Item=FuncArg> {
|
||||||
self.pos.items.into_iter().map(|item| FuncArg::Pos(item))
|
self.pos.items.into_iter().map(|item| FuncArg::Pos(item))
|
||||||
.chain(self.key.pairs.into_iter().map(|pair| FuncArg::Key(pair)))
|
.chain(self.key.pairs.into_iter().map(|pair| FuncArg::Key(pair)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Either a positional or keyword argument.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum FuncArg {
|
pub enum FuncArg {
|
||||||
|
/// A positional argument.
|
||||||
Pos(Spanned<Expr>),
|
Pos(Spanned<Expr>),
|
||||||
|
/// A keyword argument.
|
||||||
Key(Pair),
|
Key(Pair),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuncArg {
|
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 {
|
pub fn span(&self) -> Span {
|
||||||
match self {
|
match self {
|
||||||
FuncArg::Pos(item) => item.span,
|
FuncArg::Pos(item) => item.span,
|
||||||
@ -72,14 +77,17 @@ impl FuncArg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extra methods on [`Options`](Option) used for argument parsing.
|
||||||
pub trait OptionExt: Sized {
|
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> {
|
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() {
|
if self.is_none() {
|
||||||
errors.push(err!(span; "missing argument: {}", what));
|
errors.push(err!(span; "missing argument: {}", arg));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Value types for extracting function arguments.
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use toddle::query::{FontStyle, FontWeight};
|
use toddle::query::{FontStyle, FontWeight};
|
||||||
@ -10,9 +12,65 @@ use super::*;
|
|||||||
use self::AlignmentValue::*;
|
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 {
|
pub trait Value {
|
||||||
|
/// The type to parse into.
|
||||||
type Output;
|
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>;
|
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 {
|
macro_rules! value {
|
||||||
($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
|
($type:ty, $output:ty, $name:expr, $($p:pat => $r:expr),* $(,)?) => {
|
||||||
impl Value for $type {
|
impl Value for $type {
|
||||||
@ -57,6 +116,8 @@ value!(ScaleSize, Self, "number or size",
|
|||||||
Expr::Number(scale) => ScaleSize::Scaled(scale as f32),
|
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;
|
pub struct StringLike;
|
||||||
|
|
||||||
value!(StringLike, String, "identifier or string",
|
value!(StringLike, String, "identifier or string",
|
||||||
@ -64,15 +125,18 @@ value!(StringLike, String, "identifier or string",
|
|||||||
Expr::Str(s) => s,
|
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> {
|
impl<V: Value> Value for Defaultable<V> {
|
||||||
type Output = Option<T::Output>;
|
type Output = Option<V::Output>;
|
||||||
|
|
||||||
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
fn parse(expr: Spanned<Expr>) -> Result<Self::Output, Error> {
|
||||||
match expr.v {
|
match expr.v {
|
||||||
Expr::Ident(ident) if ident.as_str() == "default" => Ok(None),
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum AlignmentValue {
|
pub enum AlignmentValue {
|
||||||
|
/// A generic alignment.
|
||||||
Align(Alignment),
|
Align(Alignment),
|
||||||
Left,
|
Left,
|
||||||
Top,
|
Top,
|
||||||
@ -145,26 +213,26 @@ pub enum AlignmentValue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AlignmentValue {
|
impl AlignmentValue {
|
||||||
/// The generic axis this alignment corresponds to in the given system of
|
/// The specific axis this alignment corresponds to. `None` if the alignment
|
||||||
/// layouting axes. `None` if the alignment is generic.
|
/// is generic.
|
||||||
pub fn axis(self, axes: LayoutAxes) -> Option<GenericAxis> {
|
pub fn axis(self) -> Option<SpecificAxis> {
|
||||||
match self {
|
match self {
|
||||||
Left | Right => Some(Horizontal.to_generic(axes)),
|
Left | Right => Some(Horizontal),
|
||||||
Top | Bottom => Some(Vertical.to_generic(axes)),
|
Top | Bottom => Some(Vertical),
|
||||||
Align(_) => None,
|
Align(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The generic version of this alignment in the given system of layouting
|
/// The generic version of this alignment on the given axis in the given
|
||||||
/// axes.
|
/// system of layouting axes.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the alignment is invalid for the given axis.
|
/// Returns `None` if the alignment is invalid for the given axis.
|
||||||
pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option<Alignment> {
|
pub fn to_generic(self, axes: LayoutAxes, axis: GenericAxis) -> Option<Alignment> {
|
||||||
let specific = axis.to_specific(axes);
|
let specific = axis.to_specific(axes);
|
||||||
let start = match axes.get(axis).is_positive() {
|
let positive = axes.get(axis).is_positive();
|
||||||
true => Origin,
|
|
||||||
false => End,
|
// The alignment matching the origin of the positive coordinate direction.
|
||||||
};
|
let start = if positive { Origin } else { End };
|
||||||
|
|
||||||
match (self, specific) {
|
match (self, specific) {
|
||||||
(Align(alignment), _) => Some(alignment),
|
(Align(alignment), _) => Some(alignment),
|
||||||
@ -174,10 +242,10 @@ impl AlignmentValue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The specific version of this alignment in the given system of layouting
|
/// The specific version of this alignment on the given axis in the given
|
||||||
/// axes.
|
/// system of layouting axes.
|
||||||
pub fn to_specific(self, axes: LayoutAxes, axis: SpecificAxis) -> AlignmentValue {
|
pub fn to_specific(self, axes: LayoutAxes, axis: GenericAxis) -> AlignmentValue {
|
||||||
let direction = axes.get_specific(axis);
|
let direction = axes.get(axis);
|
||||||
if let Align(alignment) = self {
|
if let Align(alignment) = self {
|
||||||
match (direction, alignment) {
|
match (direction, alignment) {
|
||||||
(LeftToRight, Origin) | (RightToLeft, End) => Left,
|
(LeftToRight, Origin) | (RightToLeft, End) => Left,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
//! Tokenization and parsing of source code.
|
//! Syntax models, parsing and tokenization.
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@ -17,14 +17,19 @@ pub_use_mod!(parsing);
|
|||||||
pub_use_mod!(tokens);
|
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)]
|
#[async_trait(?Send)]
|
||||||
pub trait Model: Debug + ModelBounds {
|
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>>;
|
async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> Layouted<Commands<'a>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A tree representation of source code.
|
/// A tree representation of source code.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct SyntaxModel {
|
pub struct SyntaxModel {
|
||||||
|
/// The syntactical elements making up this model.
|
||||||
pub nodes: SpanVec<Node>,
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
/// A number of whitespace characters containing less than two newlines.
|
/// Whitespace containing less than two newlines.
|
||||||
Space,
|
Space,
|
||||||
/// Whitespace characters with more than two newlines.
|
/// Whitespace with more than two newlines.
|
||||||
Newline,
|
Newline,
|
||||||
/// Plain text.
|
/// Plain text.
|
||||||
Text(String),
|
Text(String),
|
||||||
/// Italics enabled / disabled.
|
/// Italics were enabled / disabled.
|
||||||
ToggleItalic,
|
ToggleItalic,
|
||||||
/// Bolder enabled / disabled.
|
/// Bolder was enabled / disabled.
|
||||||
ToggleBolder,
|
ToggleBolder,
|
||||||
/// Monospace enabled / disabled.
|
/// Monospace was enabled / disabled.
|
||||||
ToggleMonospace,
|
ToggleMonospace,
|
||||||
/// A submodel.
|
/// A submodel, typically a function invocation.
|
||||||
Model(Box<dyn Model>),
|
Model(Box<dyn Model>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,15 +90,34 @@ impl PartialEq for Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decorations for semantic syntax highlighting.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum Decoration {
|
pub enum Decoration {
|
||||||
|
/// A valid function name:
|
||||||
|
/// ```typst
|
||||||
|
/// [box]
|
||||||
|
/// ^^^
|
||||||
|
/// ```
|
||||||
ValidFuncName,
|
ValidFuncName,
|
||||||
|
|
||||||
|
/// An invalid function name:
|
||||||
|
/// ```typst
|
||||||
|
/// [blabla]
|
||||||
|
/// ^^^^^^
|
||||||
|
/// ```
|
||||||
InvalidFuncName,
|
InvalidFuncName,
|
||||||
|
|
||||||
|
/// The key of a keyword argument:
|
||||||
|
/// ```typst
|
||||||
|
/// [box: width=5cm]
|
||||||
|
/// ^^^^^
|
||||||
|
/// ```
|
||||||
ArgumentKey,
|
ArgumentKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn Model {
|
impl dyn Model {
|
||||||
|
/// Downcast this model to a concrete type implementing [`Model`].
|
||||||
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
|
pub fn downcast<T>(&self) -> Option<&T> where T: Model + 'static {
|
||||||
self.as_any().downcast_ref::<T>()
|
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 {
|
pub trait ModelBounds {
|
||||||
|
/// Convert into a `dyn Any`.
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
|
/// Check for equality with another model.
|
||||||
fn bound_eq(&self, other: &dyn Model) -> bool;
|
fn bound_eq(&self, other: &dyn Model) -> bool;
|
||||||
|
|
||||||
|
/// Clone into a boxed model trait object.
|
||||||
fn bound_clone(&self) -> Box<dyn Model>;
|
fn bound_clone(&self) -> Box<dyn Model>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Parsing of source code into syntax models.
|
||||||
|
|
||||||
use crate::error::Errors;
|
use crate::error::Errors;
|
||||||
use super::expr::*;
|
use super::expr::*;
|
||||||
use super::func::{FuncHeader, FuncArgs, FuncArg};
|
use super::func::{FuncHeader, FuncArgs, FuncArg};
|
||||||
@ -14,13 +16,19 @@ pub struct ParseContext<'a> {
|
|||||||
pub scope: &'a Scope,
|
pub scope: &'a Scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The result of parsing: Some parsed thing, errors and decorations for syntax
|
||||||
|
/// highlighting.
|
||||||
pub struct Parsed<T> {
|
pub struct Parsed<T> {
|
||||||
|
/// The result of the parsing process.
|
||||||
pub output: T,
|
pub output: T,
|
||||||
|
/// Errors that arose in the parsing process.
|
||||||
pub errors: Errors,
|
pub errors: Errors,
|
||||||
|
/// Decorations for semantic syntax highlighting.
|
||||||
pub decorations: SpanVec<Decoration>,
|
pub decorations: SpanVec<Decoration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Parsed<T> {
|
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 {
|
pub fn map<F, U>(self, f: F) -> Parsed<U> where F: FnOnce(T) -> U {
|
||||||
Parsed {
|
Parsed {
|
||||||
output: f(self.output),
|
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> {
|
pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxModel> {
|
||||||
let mut model = SyntaxModel::new();
|
let mut model = SyntaxModel::new();
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
let mut decorations = 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);
|
let mut tokens = Tokens::new(start, src, TokenizationMode::Body);
|
||||||
|
|
||||||
while let Some(token) = tokens.next() {
|
while let Some(token) = tokens.next() {
|
||||||
let span = token.span;
|
let span = token.span;
|
||||||
|
|
||||||
let node = match token.v {
|
let node = match token.v {
|
||||||
|
// Only at least two newlines mean a _real_ newline indicating a
|
||||||
|
// paragraph break.
|
||||||
Token::Space(newlines) => if newlines >= 2 {
|
Token::Space(newlines) => if newlines >= 2 {
|
||||||
Node::Newline
|
Node::Newline
|
||||||
} else {
|
} else {
|
||||||
@ -50,6 +65,9 @@ pub fn parse(start: Position, src: &str, ctx: ParseContext) -> Parsed<SyntaxMode
|
|||||||
Token::Function { header, body, terminated } => {
|
Token::Function { header, body, terminated } => {
|
||||||
let parsed: Parsed<Node> = FuncParser::new(header, body, ctx).parse();
|
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));
|
errors.extend(offset_spans(parsed.errors, span.start));
|
||||||
decorations.extend(offset_spans(parsed.decorations, 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 }
|
Parsed { output: model, errors, decorations }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs the function parsing.
|
||||||
struct FuncParser<'s> {
|
struct FuncParser<'s> {
|
||||||
ctx: ParseContext<'s>,
|
ctx: ParseContext<'s>,
|
||||||
errors: Errors,
|
errors: Errors,
|
||||||
decorations: SpanVec<Decoration>,
|
decorations: SpanVec<Decoration>,
|
||||||
|
|
||||||
|
/// ```typst
|
||||||
|
/// [tokens][body]
|
||||||
|
/// ^^^^^^
|
||||||
|
/// ```
|
||||||
tokens: Tokens<'s>,
|
tokens: Tokens<'s>,
|
||||||
peeked: Option<Option<Spanned<Token<'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>>,
|
body: Option<Spanned<&'s str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> FuncParser<'s> {
|
impl<'s> FuncParser<'s> {
|
||||||
|
/// Create a new function parser.
|
||||||
fn new(
|
fn new(
|
||||||
header: &'s str,
|
header: &'s str,
|
||||||
body: Option<Spanned<&'s str>>,
|
body: Option<Spanned<&'s str>>,
|
||||||
@ -104,11 +136,15 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Do the parsing.
|
||||||
fn parse(mut self) -> Parsed<Node> {
|
fn parse(mut self) -> Parsed<Node> {
|
||||||
let parsed = if let Some(header) = self.parse_func_header() {
|
let parsed = if let Some(header) = self.parse_func_header() {
|
||||||
let name = header.name.v.as_str();
|
let name = header.name.v.as_str();
|
||||||
let (parser, deco) = match self.ctx.scope.get_parser(name) {
|
let (parser, deco) = match self.ctx.scope.get_parser(name) {
|
||||||
|
// A valid function.
|
||||||
Ok(parser) => (parser, Decoration::ValidFuncName),
|
Ok(parser) => (parser, Decoration::ValidFuncName),
|
||||||
|
|
||||||
|
// The fallback parser was returned. Invalid function.
|
||||||
Err(parser) => {
|
Err(parser) => {
|
||||||
self.errors.push(err!(header.name.span; "unknown function"));
|
self.errors.push(err!(header.name.span; "unknown function"));
|
||||||
(parser, Decoration::InvalidFuncName)
|
(parser, Decoration::InvalidFuncName)
|
||||||
@ -139,6 +175,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the header tokens.
|
||||||
fn parse_func_header(&mut self) -> Option<FuncHeader> {
|
fn parse_func_header(&mut self) -> Option<FuncHeader> {
|
||||||
let start = self.pos();
|
let start = self.pos();
|
||||||
self.skip_whitespace();
|
self.skip_whitespace();
|
||||||
@ -166,6 +203,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
Some(FuncHeader { name, args })
|
Some(FuncHeader { name, args })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the function arguments after a colon.
|
||||||
fn parse_func_args(&mut self) -> FuncArgs {
|
fn parse_func_args(&mut self) -> FuncArgs {
|
||||||
let mut args = FuncArgs::new();
|
let mut args = FuncArgs::new();
|
||||||
|
|
||||||
@ -226,7 +264,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
arg
|
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>> {
|
fn parse_expr(&mut self) -> Option<Spanned<Expr>> {
|
||||||
let first = self.peek()?;
|
let first = self.peek()?;
|
||||||
let spanned = |v| Spanned { v, span: first.span };
|
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));
|
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(
|
fn expected_found_or_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
thing: &str,
|
thing: &str,
|
||||||
@ -315,7 +354,7 @@ impl<'s> FuncParser<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Consume tokens until the function returns true and only consume the last
|
/// 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)
|
fn eat_until<F>(&mut self, mut f: F, eat_match: bool)
|
||||||
where F: FnMut(Token<'s>) -> bool {
|
where F: FnMut(Token<'s>) -> bool {
|
||||||
while let Some(token) = self.peek() {
|
while let Some(token) = self.peek() {
|
||||||
@ -342,11 +381,12 @@ impl<'s> FuncParser<'s> {
|
|||||||
*self.peeked.get_or_insert_with(|| iter.next())
|
*self.peeked.get_or_insert_with(|| iter.next())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Peek at the unspanned value of the next token.
|
||||||
fn peekv(&mut self) -> Option<Token<'s>> {
|
fn peekv(&mut self) -> Option<Token<'s>> {
|
||||||
self.peek().map(Spanned::value)
|
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.
|
/// token.
|
||||||
fn pos(&self) -> Position {
|
fn pos(&self) -> Position {
|
||||||
self.peeked.flatten()
|
self.peeked.flatten()
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
//! Scopes containing function parsers.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
|
@ -5,18 +5,20 @@ use std::ops::{Add, Sub};
|
|||||||
use serde::Serialize;
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)]
|
||||||
pub struct Position {
|
pub struct Position {
|
||||||
/// The 0-indexed line (inclusive).
|
/// The zero-indexed line.
|
||||||
pub line: usize,
|
pub line: usize,
|
||||||
/// The 0-indexed column (inclusive).
|
/// The zero-indexed column.
|
||||||
pub column: usize,
|
pub column: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
|
/// The line 0, column 0 position.
|
||||||
pub const ZERO: Position = Position { line: 0, column: 0 };
|
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 {
|
pub fn new(line: usize, column: usize) -> Position {
|
||||||
Position { line, column }
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)]
|
||||||
pub struct Span {
|
pub struct Span {
|
||||||
|
/// The inclusive start position.
|
||||||
pub start: Position,
|
pub start: Position,
|
||||||
|
/// The inclusive end position.
|
||||||
pub end: Position,
|
pub end: Position,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
|
/// A dummy span.
|
||||||
pub const ZERO: Span = Span { start: Position::ZERO, end: Position::ZERO };
|
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 {
|
pub fn new(start: Position, end: Position) -> Span {
|
||||||
Span { start, end }
|
Span { start, end }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new span with the earlier start and later end position.
|
||||||
pub fn merge(a: Span, b: Span) -> Span {
|
pub fn merge(a: Span, b: Span) -> Span {
|
||||||
Span {
|
Span {
|
||||||
start: a.start.min(b.start),
|
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 {
|
pub fn at(pos: Position) -> Span {
|
||||||
Span { start: pos, end: pos }
|
Span { start: pos, end: pos }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand(&mut self, other: Span) {
|
/// Offset a span by a start position.
|
||||||
*self = Span::merge(*self, other)
|
///
|
||||||
}
|
/// This is, for example, used to translate error spans from function local
|
||||||
|
/// to global.
|
||||||
pub fn offset(self, start: Position) -> Span {
|
pub fn offset(self, start: Position) -> Span {
|
||||||
Span {
|
Span {
|
||||||
start: start + self.start,
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Serialize)]
|
||||||
pub struct Spanned<T> {
|
pub struct Spanned<T> {
|
||||||
|
/// The value.
|
||||||
pub v: T,
|
pub v: T,
|
||||||
|
/// The corresponding span.
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Spanned<T> {
|
impl<T> Spanned<T> {
|
||||||
|
/// Create a new instance from a value and its span.
|
||||||
pub fn new(v: T, span: Span) -> Spanned<T> {
|
pub fn new(v: T, span: Span) -> Spanned<T> {
|
||||||
Spanned { v, span }
|
Spanned { v, span }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Access the value.
|
||||||
pub fn value(self) -> T {
|
pub fn value(self) -> T {
|
||||||
self.v
|
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 {
|
pub fn map<V, F>(self, f: F) -> Spanned<V> where F: FnOnce(T) -> V {
|
||||||
Spanned { v: f(self.v), span: self.span }
|
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 {
|
pub fn map_span<F>(mut self, f: F) -> Spanned<T> where F: FnOnce(Span) -> Span {
|
||||||
self.span = f(self.span);
|
self.span = f(self.span);
|
||||||
self
|
self
|
||||||
@ -124,6 +138,8 @@ impl<T> Spanned<T> {
|
|||||||
/// A vector of spanned things.
|
/// A vector of spanned things.
|
||||||
pub type SpanVec<T> = Vec<Spanned<T>>;
|
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>> {
|
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)))
|
vec.into_iter().map(move |s| s.map_span(|span| span.offset(start)))
|
||||||
}
|
}
|
||||||
|
@ -16,16 +16,31 @@ pub enum Token<'s> {
|
|||||||
/// number of newlines that were contained in the whitespace.
|
/// number of newlines that were contained in the whitespace.
|
||||||
Space(usize),
|
Space(usize),
|
||||||
|
|
||||||
/// A line comment with inner string contents `//<&'s str>\n`.
|
/// A line comment with inner string contents `//<str>\n`.
|
||||||
LineComment(&'s str),
|
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.
|
/// can contain nested block comments.
|
||||||
BlockComment(&'s str),
|
BlockComment(&'s str),
|
||||||
|
|
||||||
/// A function invocation `[<header>][<body>]`.
|
/// A function invocation.
|
||||||
Function {
|
Function {
|
||||||
|
/// The header string:
|
||||||
|
/// ```typst
|
||||||
|
/// [header: args][body]
|
||||||
|
/// ^^^^^^^^^^^^
|
||||||
|
/// ```
|
||||||
header: &'s str,
|
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>>,
|
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,
|
terminated: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -48,7 +63,12 @@ pub enum Token<'s> {
|
|||||||
/// An identifier in a function header: `center`.
|
/// An identifier in a function header: `center`.
|
||||||
ExprIdent(&'s str),
|
ExprIdent(&'s str),
|
||||||
/// A quoted string in a function header: `"..."`.
|
/// 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`.
|
/// A number in a function header: `3.14`.
|
||||||
ExprNumber(f64),
|
ExprNumber(f64),
|
||||||
/// A size in a function header: `12pt`.
|
/// A size in a function header: `12pt`.
|
||||||
@ -110,13 +130,19 @@ pub struct Tokens<'s> {
|
|||||||
index: usize,
|
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)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||||
|
#[allow(missing_docs)]
|
||||||
pub enum TokenizationMode {
|
pub enum TokenizationMode {
|
||||||
Header,
|
Header,
|
||||||
Body,
|
Body,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Tokens<'s> {
|
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> {
|
pub fn new(start: Position, src: &'s str, mode: TokenizationMode) -> Tokens<'s> {
|
||||||
Tokens {
|
Tokens {
|
||||||
src,
|
src,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user