diff --git a/Cargo.toml b/Cargo.toml index c2f6b9214..53f6a2e21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ fs = ["fontdock/fs"] [dev-dependencies] futures-executor = "0.3" serde_json = "1" -raqote = { version = "0.7", default-features = false } +raqote = { version = "0.8", default-features = false } [[test]] name = "test-typeset" diff --git a/main/main.rs b/main/main.rs index a04b163c4..0d67241c3 100644 --- a/main/main.rs +++ b/main/main.rs @@ -1,44 +1,37 @@ use std::cell::RefCell; -use std::error::Error; -use std::fs::{File, read_to_string}; +use std::fs::{read_to_string, File}; use std::io::BufWriter; use std::path::{Path, PathBuf}; use std::rc::Rc; -use futures_executor::block_on; use fontdock::fs::{FsIndex, FsProvider}; use fontdock::FontLoader; -use typstc::Typesetter; -use typstc::font::DynProvider; +use futures_executor::block_on; + use typstc::export::pdf; +use typstc::font::DynProvider; +use typstc::Typesetter; fn main() { - if let Err(err) = run() { - eprintln!("error: {}", err); - std::process::exit(1); - } -} - -fn run() -> Result<(), Box> { - let args: Vec = std::env::args().collect(); + let args: Vec<_> = std::env::args().collect(); if args.len() < 2 || args.len() > 3 { println!("Usage: typst src.typ [out.pdf]"); - std::process::exit(0); + return; } - let source = Path::new(&args[1]); - let dest = if args.len() <= 2 { - source.with_extension("pdf") + let src_path = Path::new(&args[1]); + let dest_path = if args.len() <= 2 { + src_path.with_extension("pdf") } else { PathBuf::from(&args[2]) }; - if source == dest { - Err("source and destination path are the same")?; + if src_path == dest_path { + panic!("source and destination path are the same"); } - let src = read_to_string(source) - .map_err(|_| "failed to read from source file")?; + let src = read_to_string(src_path) + .expect("failed to read from source file"); let mut index = FsIndex::new(); index.search_dir("fonts"); @@ -53,8 +46,10 @@ fn run() -> Result<(), Box> { let typesetter = Typesetter::new(loader.clone()); let layouts = block_on(typesetter.typeset(&src)).output; - let writer = BufWriter::new(File::create(&dest)?); - pdf::export(&layouts, &loader, writer)?; + let file = File::create(&dest_path) + .expect("failed to create output file"); - Ok(()) + let writer = BufWriter::new(file); + pdf::export(&layouts, &loader, writer) + .expect("failed to export pdf"); } diff --git a/src/diagnostic.rs b/src/diagnostic.rs index fc332aac5..2c649dac5 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -1,8 +1,8 @@ -//! Diagnostics (errors / warnings) in source code. +//! Diagnostics for source code. //! //! There are no fatal errors. 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. +//! layout on a best effort process, generating diagnostics for incorrect +//! things. #[cfg(feature = "serialize")] use serde::Serialize; diff --git a/src/export/pdf.rs b/src/export/pdf.rs index 84023c71b..cd417a9cf 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -1,28 +1,31 @@ -//! Exporting of layouts into _PDF_ documents. +//! Exporting into _PDF_ documents. use std::collections::HashMap; use std::io::{self, Write}; -use tide::{PdfWriter, Rect, Ref, Trailer, Version}; +use fontdock::FaceId; use tide::content::Content; use tide::doc::{Catalog, Page, PageTree, Resource, Text}; use tide::font::{ - CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags, Type0Font, - CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord, + CIDFont, CIDFontType, CIDSystemInfo, CMap, CMapEncoding, FontDescriptor, + FontFlags, FontStream, GlyphUnit, Type0Font, WidthRecord, }; - -use fontdock::FaceId; +use tide::{PdfWriter, Rect, Ref, Trailer, Version}; use ttf_parser::{name_id, GlyphId}; -use crate::SharedFontLoader; -use crate::layout::{MultiLayout, BoxLayout}; use crate::layout::elements::LayoutElement; +use crate::layout::{BoxLayout, MultiLayout}; use crate::length::Length; +use crate::SharedFontLoader; -/// Export a layouted list of boxes. The same font loader as used for -/// layouting needs to be passed in here since the layout only contains -/// indices referencing the loaded faces. The raw PDF ist written into the -/// target writable, returning the number of bytes written. +/// Export a list of layouts into a _PDF_ document. +/// +/// This creates one page per layout. Additionally to the layouts, you need to +/// pass in the font loader used for typesetting such that the fonts can be +/// included in the _PDF_. +/// +/// The raw _PDF_ is written into the `target` writable, returning the number of +/// bytes written. pub fn export( layout: &MultiLayout, loader: &SharedFontLoader, @@ -31,22 +34,20 @@ pub fn export( PdfExporter::new(layout, loader, target)?.write() } -/// The data relevant to the export of one document. struct PdfExporter<'a, W: Write> { writer: PdfWriter, layouts: &'a MultiLayout, loader: &'a SharedFontLoader, - /// Since we cross-reference pages and faces with their IDs already in the - /// document catalog, we need to know exactly which ID is used for what from - /// the beginning. Thus, we compute a range for each category of object and - /// stored these here. + /// We need to know exactly which indirect reference id will be used for + /// which objects up-front to correctly declare the document catalogue, page + /// tree and so on. These offsets are computed in the beginning and stored + /// here. offsets: Offsets, // Font remapping, see below at `remap_fonts`. to_pdf: HashMap, - to_fontdock: Vec, + to_layout: Vec, } -/// Indicates which range of PDF IDs will be used for which contents. struct Offsets { catalog: Ref, page_tree: Ref, @@ -58,27 +59,24 @@ struct Offsets { const NUM_OBJECTS_PER_FONT: u32 = 5; impl<'a, W: Write> PdfExporter<'a, W> { - /// Prepare the export. Only once [`ExportProcess::write`] is called the - /// writing really happens. fn new( layouts: &'a MultiLayout, loader: &'a SharedFontLoader, target: W, - ) -> io::Result> { + ) -> io::Result { let (to_pdf, to_fontdock) = remap_fonts(layouts); let offsets = calculate_offsets(layouts.len(), to_pdf.len()); - Ok(PdfExporter { + Ok(Self { writer: PdfWriter::new(target), layouts, offsets, to_pdf, - to_fontdock, + to_layout: to_fontdock, loader, }) } - /// Write everything (writing entry point). fn write(&mut self) -> io::Result { self.writer.write_header(Version::new(1, 7))?; self.write_preface()?; @@ -89,14 +87,13 @@ impl<'a, W: Write> PdfExporter<'a, W> { Ok(self.writer.written()) } - /// Write the document catalog and page tree. fn write_preface(&mut self) -> io::Result<()> { // The document catalog. self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?; // The font resources. let start = self.offsets.fonts.0; - let fonts = (0 .. self.to_pdf.len() as u32).map(|i| { + let fonts = (0..self.to_pdf.len() as u32).map(|i| { Resource::Font(i + 1, start + (NUM_OBJECTS_PER_FONT * i)) }); @@ -109,11 +106,10 @@ impl<'a, W: Write> PdfExporter<'a, W> { )?; // The page objects (non-root nodes in the page tree). - let iter = ids(self.offsets.pages) + for ((page_id, content_id), page) in ids(self.offsets.pages) .zip(ids(self.offsets.contents)) - .zip(self.layouts); - - for ((page_id, content_id), page) in iter { + .zip(self.layouts) + { let rect = Rect::new( 0.0, 0.0, @@ -132,7 +128,6 @@ impl<'a, W: Write> PdfExporter<'a, W> { Ok(()) } - /// Write the contents of all pages. fn write_pages(&mut self) -> io::Result<()> { for (id, page) in ids(self.offsets.contents).zip(self.layouts) { self.write_page(id, &page)?; @@ -140,11 +135,11 @@ impl<'a, W: Write> PdfExporter<'a, W> { Ok(()) } - /// Write the content of a page. fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> { - // Moves and face switches are always cached and only flushed once - // needed. let mut text = Text::new(); + + // Font switching actions are only written when the face used for + // shaped text changes. Hence, we need to remember the active face. let mut face = FaceId::MAX; let mut size = 0.0; @@ -163,7 +158,7 @@ impl<'a, W: Write> PdfExporter<'a, W> { let x = Length::raw(pos.x).as_pt(); let y = Length::raw(page.size.y - pos.y - size).as_pt(); text.tm(1.0, 0.0, 0.0, 1.0, x as f32, y as f32); - text.tj(shaped.encode_glyphs()); + text.tj(shaped.encode_glyphs_be()); } } } @@ -173,11 +168,10 @@ impl<'a, W: Write> PdfExporter<'a, W> { Ok(()) } - /// Write all the fonts. fn write_fonts(&mut self) -> io::Result<()> { let mut id = self.offsets.fonts.0; - for &face_id in &self.to_fontdock { + for &face_id in &self.to_layout { let loader = self.loader.borrow(); let face = loader.get_loaded(face_id); @@ -223,6 +217,23 @@ impl<'a, W: Write> PdfExporter<'a, W> { flags.insert(FontFlags::SYMBOLIC); flags.insert(FontFlags::SMALL_CAP); + let num_glyphs = face.number_of_glyphs(); + let widths: Vec<_> = (0..num_glyphs) + .map(|g| face.glyph_hor_advance(GlyphId(g)).unwrap_or(0)) + .map(|w| to_glyph_unit(w as f64)) + .collect(); + + let mut mapping = vec![]; + for subtable in face.character_mapping_subtables() { + subtable.codepoints(|n| { + if let Some(c) = std::char::from_u32(n) { + if let Some(g) = face.glyph_index(c) { + mapping.push((g.0, c)); + } + } + }) + } + // Write the base font object referencing the CID font. self.writer.write_obj( id, @@ -234,12 +245,6 @@ impl<'a, W: Write> PdfExporter<'a, W> { .to_unicode(id + 3), )?; - let num_glyphs = face.number_of_glyphs(); - let widths: Vec<_> = (0 .. num_glyphs) - .map(|g| face.glyph_hor_advance(GlyphId(g)).unwrap_or(0)) - .map(|w| to_glyph_unit(w as f64)) - .collect(); - // Write the CID font referencing the font descriptor. self.writer.write_obj( id + 1, @@ -252,8 +257,7 @@ impl<'a, W: Write> PdfExporter<'a, W> { .widths(vec![WidthRecord::Start(0, widths)]), )?; - // Write the font descriptor (contains the global information about - // the font). + // Write the font descriptor (contains metrics about the font). self.writer.write_obj(id + 2, FontDescriptor::new(base_font, flags, italic_angle) .font_bbox(bbox) @@ -264,25 +268,15 @@ impl<'a, W: Write> PdfExporter<'a, W> { .font_file_2(id + 4) )?; - let mut mapping = vec![]; - for subtable in face.character_mapping_subtables() { - subtable.codepoints(|n| { - if let Some(c) = std::char::from_u32(n) { - if let Some(g) = face.glyph_index(c) { - mapping.push((g.0, c)); - } - } - }) - } - - // Write the CMap, which maps glyph ID's to unicode codepoints. + // Write the CMap, which maps glyph ids back to unicode codepoints + // to enable copying out of the PDF. self.writer.write_obj(id + 3, &CMap::new( "Custom", system_info, mapping, ))?; - // Finally write the subsetted font bytes. + // Write the face's bytes. self.writer.write_obj(id + 4, &FontStream::new(face.data()))?; id += NUM_OBJECTS_PER_FONT; @@ -294,28 +288,28 @@ impl<'a, W: Write> PdfExporter<'a, W> { /// Assigns a new PDF-internal index to each used face and returns two mappings: /// - Forwards from the old face ids to the new pdf indices (hash map) -/// - Backwards from the pdf indices to the old ids (vec) +/// - Backwards from the pdf indices to the old face ids (vec) fn remap_fonts(layouts: &MultiLayout) -> (HashMap, Vec) { let mut to_pdf = HashMap::new(); - let mut to_fontdock = vec![]; + let mut to_layout = vec![]; - // We want to find out which fonts are used at all. To do that, look at each - // text element to find out which font is uses. + // We want to find out which font faces are used at all. To do that, look at + // each text element to find out which face is uses. for layout in layouts { for (_, element) in &layout.elements.0 { let LayoutElement::Text(shaped) = element; to_pdf.entry(shaped.face).or_insert_with(|| { - let next_id = to_fontdock.len(); - to_fontdock.push(shaped.face); + let next_id = to_layout.len(); + to_layout.push(shaped.face); next_id }); } } - (to_pdf, to_fontdock) + (to_pdf, to_layout) } -/// We need to know in advance which IDs to use for which objects to +/// We need to know in advance which ids to use for which objects to /// cross-reference them. Therefore, we calculate the indices in the beginning. fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets { let catalog = 1; @@ -333,7 +327,6 @@ fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets { } } -/// Create an iterator from a reference pair. -fn ids((start, end): (Ref, Ref)) -> impl Iterator { - start ..= end +fn ids((start, end): (Ref, Ref)) -> impl Iterator { + start..=end } diff --git a/src/font.rs b/src/font.rs index 5d81b6fc3..c37c913ee 100644 --- a/src/font.rs +++ b/src/font.rs @@ -3,14 +3,15 @@ use std::cell::RefCell; use std::ops::Deref; use std::rc::Rc; + +use fontdock::{ContainsChar, FaceFromVec, FontLoader, FontProvider}; use ttf_parser::Face; -use fontdock::{FontLoader, FontProvider, ContainsChar, FaceFromVec}; /// A referenced-count shared font loader backed by a dynamic provider. pub type SharedFontLoader = Rc>>>; /// The dynamic font provider type backing the font loader. -pub type DynProvider = dyn FontProvider; +pub type DynProvider = dyn FontProvider; /// An owned font face. pub struct OwnedFace { @@ -18,6 +19,13 @@ pub struct OwnedFace { face: Face<'static>, } +impl OwnedFace { + /// The raw face data. + pub fn data(&self) -> &[u8] { + &self.data + } +} + impl FaceFromVec for OwnedFace { fn from_vec(vec: Vec, i: u32) -> Option { // The vec's location is stable in memory since we don't touch it and @@ -26,20 +34,13 @@ impl FaceFromVec for OwnedFace { std::slice::from_raw_parts(vec.as_ptr(), vec.len()) }; - Some(OwnedFace { + Some(Self { data: vec, face: Face::from_slice(slice, i).ok()?, }) } } -impl OwnedFace { - /// The raw face data. - pub fn data(&self) -> &[u8] { - &self.data - } -} - impl ContainsChar for OwnedFace { fn contains_char(&self, c: char) -> bool { self.glyph_index(c).is_some() diff --git a/src/func.rs b/src/func.rs index bb2862d28..098708950 100644 --- a/src/func.rs +++ b/src/func.rs @@ -1,20 +1,20 @@ //! Tools for building custom functions. -use crate::Feedback; -use crate::syntax::span::{Span, Spanned}; -use crate::syntax::parsing::{parse, ParseState}; -use crate::syntax::tree::SyntaxTree; - /// Useful things for creating functions. pub mod prelude { pub use crate::layout::prelude::*; pub use crate::layout::Command::{self, *}; - pub use crate::syntax::prelude::*; pub use crate::style::*; - pub use super::{OptionExt, parse_maybe_body, expect_no_body}; + pub use crate::syntax::prelude::*; + pub use super::{expect_no_body, parse_maybe_body, OptionExt}; } -/// Extra methods on [`Options`](Option) used for function argument parsing. +use crate::syntax::parsing::{parse, ParseState}; +use crate::syntax::span::{Span, Spanned}; +use crate::syntax::tree::SyntaxTree; +use crate::Feedback; + +/// Extra methods on `Option`s used for function argument parsing. pub trait OptionExt: Sized { /// Calls `f` with `val` if this is `Some(val)`. fn with(self, f: impl FnOnce(T)); @@ -62,8 +62,8 @@ pub fn expect_no_body(body: Option>, f: &mut Feedback) { /// Implement a custom function concisely. /// /// # Examples -/// Look at the source code of the [`library`](crate::library) module for -/// examples on how the macro works. +/// Look at the source code of the `library` module for examples on how the +/// macro works. #[macro_export] macro_rules! function { // Entry point. @@ -85,7 +85,7 @@ macro_rules! function { // Parse trait. (@parse($($a:tt)*) parse(default) $($r:tt)*) => { - function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) {Default::default() } $($r)*); + function!(@parse($($a)*) parse(_h, _b, _c, _f, _m) { Default::default() } $($r)*); }; (@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => { function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*); @@ -104,7 +104,10 @@ macro_rules! function { #[allow(unused)] mut call: $crate::syntax::parsing::FuncCall, #[allow(unused)] $state: &$crate::syntax::parsing::ParseState, #[allow(unused)] $metadata: Self::Meta, - ) -> $crate::Pass where Self: Sized { + ) -> $crate::Pass + where + Self: Sized, + { let mut feedback = $crate::Feedback::new(); #[allow(unused)] let $header = &mut call.header; #[allow(unused)] let $body = call.body; diff --git a/src/geom.rs b/src/geom.rs index 5e65446e1..77b1ec114 100644 --- a/src/geom.rs +++ b/src/geom.rs @@ -16,32 +16,24 @@ pub struct Value2 { impl Value2 { /// Create a new 2D-value from two values. - pub fn new(x: T, y: T) -> Value2 { Value2 { x, y } } + pub fn new(x: T, y: T) -> Self { + Self { x, y } + } /// Create a new 2D-value with `x` set to a value and `y` to default. - pub fn with_x(x: T) -> Value2 where T: Default { - Value2 { x, y: T::default() } + pub fn with_x(x: T) -> Self where T: Default { + Self { x, y: T::default() } } /// Create a new 2D-value with `y` set to a value and `x` to default. - pub fn with_y(y: T) -> Value2 where T: Default { - Value2 { x: T::default(), y } - } - - /// Create a new 2D-value with the primary axis set to a value and the other - /// one to default. - pub fn with_primary(v: T, axes: LayoutAxes) -> Value2 where T: Default { - Value2::with_x(v).generalized(axes) - } - - /// Create a new 2D-value with the secondary axis set to a value and the - /// other one to default. - pub fn with_secondary(v: T, axes: LayoutAxes) -> Value2 where T: Default { - Value2::with_y(v).generalized(axes) + pub fn with_y(y: T) -> Self where T: Default { + Self { x: T::default(), y } } /// Create a 2D-value with `x` and `y` set to the same value `s`. - pub fn with_all(s: T) -> Value2 { Value2 { x: s.clone(), y: s } } + pub fn with_all(s: T) -> Self { + Self { x: s.clone(), y: s } + } /// Get the specificed component. pub fn get(self, axis: SpecAxis) -> T { @@ -83,16 +75,16 @@ impl Value2 { /// axes, that is: /// - `x` describes the primary axis instead of the horizontal one. /// - `y` describes the secondary axis instead of the vertical one. - pub fn generalized(self, axes: LayoutAxes) -> Value2 { + pub fn generalized(self, axes: LayoutAxes) -> Self { match axes.primary.axis() { Horizontal => self, - Vertical => Value2 { x: self.y, y: self.x }, + Vertical => Self { x: self.y, y: self.x }, } } /// Returns the specialized version of this generalized Size2D (inverse to /// `generalized`). - pub fn specialized(self, axes: LayoutAxes) -> Value2 { + pub fn specialized(self, axes: LayoutAxes) -> Self { // In fact, generalized is its own inverse. For reasons of clarity // at the call site, we still have this second function. self.generalized(axes) @@ -104,7 +96,7 @@ impl Value2 { } } -impl Debug for Value2 where T: Debug { +impl Debug for Value2 { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.debug_list() .entry(&self.x) @@ -118,16 +110,16 @@ pub type Size = Value2; impl Size { /// The zeroed size. - pub const ZERO: Size = Size { x: 0.0, y: 0.0 }; + pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; /// Whether the given size fits into this one, that is, both coordinate /// values are smaller or equal. - pub fn fits(self, other: Size) -> bool { + pub fn fits(self, other: Self) -> bool { self.x >= other.x && self.y >= other.y } /// Return a size padded by the paddings of the given box. - pub fn padded(self, padding: Margins) -> Size { + pub fn padded(self, padding: Margins) -> Self { Size { x: self.x + padding.left + padding.right, y: self.y + padding.top + padding.bottom, @@ -135,7 +127,7 @@ impl Size { } /// Return a size reduced by the paddings of the given box. - pub fn unpadded(self, padding: Margins) -> Size { + pub fn unpadded(self, padding: Margins) -> Self { Size { x: self.x - padding.left - padding.right, y: self.y - padding.top - padding.bottom, @@ -147,7 +139,7 @@ impl Size { /// /// This assumes the size to be generalized such that `x` corresponds to the /// primary axis. - pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Size { + pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Self { Size { x: anchor(self.x, align.primary, axes.primary), y: anchor(self.y, align.secondary, axes.secondary), @@ -189,17 +181,17 @@ pub struct Value4 { impl Value4 { /// Create a new box from four sizes. - pub fn new(left: T, top: T, right: T, bottom: T) -> Value4 { + pub fn new(left: T, top: T, right: T, bottom: T) -> Self { Value4 { left, top, right, bottom } } /// Create a box with all four fields set to the same value `s`. - pub fn with_all(value: T) -> Value4 { + pub fn with_all(value: T) -> Self { Value4 { left: value.clone(), top: value.clone(), right: value.clone(), - bottom: value + bottom: value, } } @@ -213,7 +205,7 @@ impl Value4 { } match dir { - LTT => &mut self.left, + LTR => &mut self.left, RTL => &mut self.right, TTB => &mut self.top, BTT => &mut self.bottom, @@ -224,18 +216,6 @@ impl Value4 { pub fn set_all(&mut self, value: T) { *self = Value4::with_all(value); } - - /// Set the `left` and `right` values. - pub fn set_horizontal(&mut self, value: T) { - self.left = value.clone(); - self.right = value; - } - - /// Set the `top` and `bottom` values. - pub fn set_vertical(&mut self, value: T) { - self.top = value.clone(); - self.bottom = value; - } } /// A length in four dimensions. diff --git a/src/layout/elements.rs b/src/layout/elements.rs index 92b53ae89..b4a6b31c8 100644 --- a/src/layout/elements.rs +++ b/src/layout/elements.rs @@ -1,19 +1,20 @@ -//! The elements layouts are composed of. +//! Basic building blocks of layouts. use std::fmt::{self, Debug, Formatter}; -use ttf_parser::GlyphId; use fontdock::FaceId; +use ttf_parser::GlyphId; + use crate::geom::Size; -/// A sequence of positioned layout elements. -#[derive(Debug, Clone, PartialEq)] +/// A collection of absolutely positioned layout elements. +#[derive(Debug, Default, Clone, PartialEq)] pub struct LayoutElements(pub Vec<(Size, LayoutElement)>); impl LayoutElements { - /// Create an empty sequence. + /// Create an new empty collection. pub fn new() -> Self { - LayoutElements(vec![]) + Self(vec![]) } /// Add an element at a position. @@ -21,7 +22,9 @@ impl LayoutElements { self.0.push((pos, element)); } - /// Add a sequence of elements offset by an `offset`. + /// Add all elements of another collection, offsetting each by the given + /// `offset`. This can be used to place a sublayout at a position in another + /// layout. pub fn extend_offset(&mut self, offset: Size, more: Self) { for (subpos, element) in more.0 { self.0.push((subpos + offset, element)); @@ -29,16 +32,9 @@ impl LayoutElements { } } -impl Default for LayoutElements { - fn default() -> Self { - Self::new() - } -} - -/// A layout element, which is the basic building block layouts are composed of. +/// A layout element, the basic building block layouts are composed of. #[derive(Debug, Clone, PartialEq)] pub enum LayoutElement { - /// Shaped text. Text(Shaped), } @@ -48,14 +44,17 @@ pub struct Shaped { pub text: String, pub face: FaceId, pub glyphs: Vec, + /// The horizontal offsets of the glyphs with the same indices. Vertical + /// offets are not yet supported. pub offsets: Vec, + /// The font size. pub size: f64, } impl Shaped { - /// Create an empty shape run. - pub fn new(face: FaceId, size: f64) -> Shaped { - Shaped { + /// Create a new shape run with empty `text`, `glyphs` and `offsets`. + pub fn new(face: FaceId, size: f64) -> Self { + Self { text: String::new(), face, glyphs: vec![], @@ -65,12 +64,11 @@ impl Shaped { } /// Encode the glyph ids into a big-endian byte buffer. - pub fn encode_glyphs(&self) -> Vec { - const BYTES_PER_GLYPH: usize = 2; - let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * self.glyphs.len()); - for g in &self.glyphs { - bytes.push((g.0 >> 8) as u8); - bytes.push((g.0 & 0xff) as u8); + pub fn encode_glyphs_be(&self) -> Vec { + let mut bytes = Vec::with_capacity(2 * self.glyphs.len()); + for &GlyphId(g) in &self.glyphs { + bytes.push((g >> 8) as u8); + bytes.push((g & 0xff) as u8); } bytes } diff --git a/src/layout/line.rs b/src/layout/line.rs index 358d2ac91..069a4e561 100644 --- a/src/layout/line.rs +++ b/src/layout/line.rs @@ -1,70 +1,67 @@ -//! The line layouter arranges boxes into lines. +//! Arranging 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. +//! Along the primary axis, the boxes are laid out next to each other as long as +//! they fit into a line. When necessary, a line break is inserted and the new +//! line is offset along the secondary axis by the height of the previous line +//! plus extra line spacing. //! -//! Internally, the line layouter uses a stack layouter to arrange the finished -//! lines. +//! Internally, the line layouter uses a stack layouter to stack the finished +//! lines on top of each. -use super::stack::{StackLayouter, StackContext}; +use super::stack::{StackContext, StackLayouter}; use super::*; /// Performs the line layouting. -#[derive(Debug)] pub struct LineLayouter { - /// The context for layouting. ctx: LineContext, - /// The underlying stack layouter. stack: StackLayouter, - /// The currently written line. + /// The in-progress line. run: LineRun, } /// The context for line layouting. #[derive(Debug, Clone)] pub struct LineContext { - /// The spaces to layout in. + /// The spaces to layout into. pub spaces: LayoutSpaces, - /// The initial layouting axes, which can be updated by the - /// [`LineLayouter::set_axes`] method. + /// The initial layouting axes, which can be updated through `set_axes`. pub axes: LayoutAxes, - /// Which alignment to set on the resulting layout. This affects how it will - /// be positioned in a parent box. + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, - /// Whether to have repeated spaces or to use only the first and only once. + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. pub repeat: bool, - /// The line spacing. + /// The spacing to be inserted between each pair of lines. pub line_spacing: f64, } -/// 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)] +/// A sequence of boxes with the same alignment. A real line can consist of +/// multiple runs with different alignments. struct LineRun { - /// The so-far accumulated layouts in the line. + /// The so-far accumulated items of the run. layouts: Vec<(f64, BoxLayout)>, - /// The width and maximal height of the line. + /// The summed width and maximal height of the run. size: Size, /// 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. + /// When a new run is created the alignment is yet to be determined and + /// `None` as such. Once a layout is added, its alignment decides the + /// alignment for the whole run. align: Option, - /// If another line run with different alignment already took up some space - /// of the line, this run has less space and how much is stored here. + /// The amount of space left by another run on the same line or `None` if + /// this is the only run so far. usable: Option, - /// A possibly cached soft spacing or spacing state. + /// The spacing state. This influences how new spacing is handled, e.g. hard + /// spacing may override soft spacing. last_spacing: LastSpacing, } impl LineLayouter { /// Create a new line layouter. - pub fn new(ctx: LineContext) -> LineLayouter { - LineLayouter { + pub fn new(ctx: LineContext) -> Self { + Self { stack: StackLayouter::new(StackContext { spaces: ctx.spaces.clone(), axes: ctx.axes, @@ -76,14 +73,14 @@ impl LineLayouter { } } - /// Add a layout to the run. + /// Add a layout. pub fn add(&mut self, layout: BoxLayout) { let axes = self.ctx.axes; if let Some(align) = self.run.align { if layout.align.secondary != align.secondary { // TODO: Issue warning for non-fitting alignment in - // non-repeating context. + // non-repeating context. let fitting = self.stack.is_fitting_alignment(layout.align); if !fitting && self.ctx.repeat { self.finish_space(true); @@ -92,7 +89,6 @@ impl LineLayouter { } } else if layout.align.primary < align.primary { self.finish_line(); - } else if layout.align.primary > align.primary { let mut rest_run = LineRun::new(); @@ -137,25 +133,24 @@ impl LineLayouter { self.run.last_spacing = LastSpacing::None; } - /// Add multiple layouts to the run. + /// Add multiple layouts. /// - /// This function simply calls `add` repeatedly for each layout. + /// This is equivalent to calling `add` repeatedly for each layout. pub fn add_multiple(&mut self, layouts: MultiLayout) { for layout in layouts { self.add(layout); } } - /// The remaining usable size of the run. + /// The remaining usable size of the line. /// - /// This specifies how much more fits before a line break needs to be - /// issued. + /// This specifies how much more would fit before a line break would be + /// needed. fn usable(&self) -> Size { - // The base is the usable space per stack layouter. + // The base is the usable space of the stack layouter. let mut usable = self.stack.usable().generalized(self.ctx.axes); - // If this is a alignment-continuing line, we override the primary - // usable size. + // If there was another run already, override the stack's size. if let Some(primary) = self.run.usable { usable.x = primary; } @@ -164,18 +159,17 @@ impl LineLayouter { usable } - /// Add spacing along the primary axis to the line. + /// Add spacing to the line. pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { - // A hard space is simply an empty box. SpacingKind::Hard => { spacing = spacing.min(self.usable().x); self.run.size.x += spacing; self.run.last_spacing = LastSpacing::Hard; } - // A soft space is cached if it is not consumed by a hard space or - // previous soft space with higher level. + // A soft space is cached since it might be consumed by a hard + // spacing. SpacingKind::Soft(level) => { let consumes = match self.run.last_spacing { LastSpacing::None => true, @@ -190,23 +184,23 @@ impl LineLayouter { } } - /// Finish the line and add secondary spacing to the underlying stack. + /// Finish the line and add spacing to the underlying stack. pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) { self.finish_line_if_not_empty(); self.stack.add_spacing(spacing, kind) } - /// Update the layouting axes used by this layouter. + /// Update the layouting axes. pub fn set_axes(&mut self, axes: LayoutAxes) { self.finish_line_if_not_empty(); self.ctx.axes = axes; self.stack.set_axes(axes) } - /// Update the layouting spaces to use. + /// Update the layouting spaces. /// /// 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 out into it yet. Otherwise, the followup spaces are /// replaced. pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); @@ -217,13 +211,11 @@ impl LineLayouter { self.ctx.line_spacing = line_spacing; } - /// 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. + /// The remaining inner spaces. If something is laid out into these spaces, + /// it will fit into this layouter's underlying stack. pub fn remaining(&self) -> LayoutSpaces { let mut spaces = self.stack.remaining(); - *spaces[0].size.secondary_mut(self.ctx.axes) - -= self.run.size.y; + *spaces[0].size.secondary_mut(self.ctx.axes) -= self.run.size.y; spaces } @@ -232,13 +224,13 @@ impl LineLayouter { self.run.size == Size::ZERO && self.run.layouts.is_empty() } - /// Finish the last line and compute the final list of boxes. + /// Finish everything up and return the final collection of boxes. pub fn finish(mut self) -> MultiLayout { self.finish_line_if_not_empty(); self.stack.finish() } - /// Finish the currently active space and start a new one. + /// Finish the active space and start a new one. /// /// At the top level, this is a page break. pub fn finish_space(&mut self, hard: bool) { @@ -246,7 +238,7 @@ impl LineLayouter { self.stack.finish_space(hard) } - /// Finish the line and start a new one. + /// Finish the active line and start a new one. pub fn finish_line(&mut self) { let mut elements = LayoutElements::new(); @@ -265,9 +257,8 @@ impl LineLayouter { self.stack.add(BoxLayout { size: self.run.size.specialized(self.ctx.axes), - align: self.run.align - .unwrap_or(LayoutAlign::new(Start, Start)), - elements + align: self.run.align.unwrap_or(LayoutAlign::new(Start, Start)), + elements }); self.run = LineRun::new(); @@ -275,7 +266,6 @@ impl LineLayouter { self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE); } - /// Finish the current line if it is not empty. fn finish_line_if_not_empty(&mut self) { if !self.line_is_empty() { self.finish_line() @@ -284,8 +274,8 @@ impl LineLayouter { } impl LineRun { - fn new() -> LineRun { - LineRun { + fn new() -> Self { + Self { layouts: vec![], size: Size::ZERO, align: None, diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 143f1984d..8a68a6a5d 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,37 +1,37 @@ -//! Layouting types and engines. - -use async_trait::async_trait; - -use crate::Pass; -use crate::font::SharedFontLoader; -use crate::geom::{Size, Margins}; -use crate::style::{LayoutStyle, TextStyle, PageStyle}; -use crate::syntax::tree::SyntaxTree; - -use elements::LayoutElements; -use tree::TreeLayouter; -use prelude::*; +//! Layouting of syntax trees into box layouts. pub mod elements; pub mod line; pub mod primitive; pub mod stack; pub mod text; -pub mod tree; - -pub use primitive::*; +mod tree; /// Basic types used across the layouting engine. pub mod prelude { - pub use super::layout; pub use super::primitive::*; + pub use super::layout; pub use Dir::*; - pub use GenAxis::*; - pub use SpecAxis::*; pub use GenAlign::*; + pub use GenAxis::*; pub use SpecAlign::*; + pub use SpecAxis::*; } +pub use primitive::*; +pub use tree::layout_tree as layout; + +use async_trait::async_trait; + +use crate::font::SharedFontLoader; +use crate::geom::{Margins, Size}; +use crate::style::{LayoutStyle, PageStyle, TextStyle}; +use crate::syntax::tree::SyntaxTree; +use crate::Pass; + +use elements::LayoutElements; +use prelude::*; + /// A collection of layouts. pub type MultiLayout = Vec; @@ -40,47 +40,41 @@ pub type MultiLayout = Vec; pub struct BoxLayout { /// The size of the box. pub size: Size, - /// How to align this layout in a parent container. + /// How to align this box in a parent container. pub align: LayoutAlign, /// The elements composing this layout. pub elements: LayoutElements, } -/// Layouting of elements. +/// Comamnd-based layout. #[async_trait(?Send)] pub trait Layout { - /// Layout self into a sequence of layouting commands. - async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass>; -} - -/// Layout a syntax tree into a list of boxes. -pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_>) -> Pass { - let mut layouter = TreeLayouter::new(ctx); - layouter.layout_tree(tree).await; - layouter.finish() + /// Create a sequence of layouting commands to execute. + async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass>; } /// The context for layouting. #[derive(Debug, Clone)] pub struct LayoutContext<'a> { - /// The font loader to retrieve fonts from when typesetting text - /// using [`layout_text`]. + /// The font loader to query fonts from when typesetting text. pub loader: &'a SharedFontLoader, /// The style for pages and text. pub style: &'a LayoutStyle, - /// The base unpadded size of this container (for relative sizing). + /// The unpadded size of this container (the base 100% for relative sizes). pub base: Size, - /// The spaces to layout in. + /// The spaces to layout into. pub spaces: LayoutSpaces, - /// Whether to have repeated spaces or to use only the first and only once. + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. pub repeat: bool, - /// The initial axes along which content is laid out. + /// The axes along which content is laid out. pub axes: LayoutAxes, - /// The alignment of the finished layout. + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, - /// Whether the layout that is to be created will be nested in a parent - /// container. - pub nested: bool, + /// Whether this layouting process is the root page-building process. + pub root: bool, } /// A collection of layout spaces. @@ -89,17 +83,17 @@ pub type LayoutSpaces = Vec; /// The space into which content is laid out. #[derive(Debug, Copy, Clone, PartialEq)] pub struct LayoutSpace { - /// The maximum size of the box to layout in. + /// The maximum size of the rectangle to layout into. pub size: Size, /// Padding that should be respected on each side. pub padding: Margins, /// Whether to expand the size of the resulting layout to the full size of - /// this space or to shrink them to fit the content. + /// this space or to shrink it to fit the content. pub expansion: LayoutExpansion, } impl LayoutSpace { - /// The offset from the origin to the start of content, that is, + /// The offset from the origin to the start of content, i.e. /// `(padding.left, padding.top)`. pub fn start(&self) -> Size { Size::new(self.padding.left, self.padding.top) @@ -110,9 +104,10 @@ impl LayoutSpace { self.size.unpadded(self.padding) } - /// A layout space without padding and size reduced by the padding. - pub fn usable_space(&self) -> LayoutSpace { - LayoutSpace { + /// The inner layout space with size reduced by the padding, zero padding of + /// its own and no layout expansion. + pub fn inner(&self) -> Self { + Self { size: self.usable(), padding: Margins::ZERO, expansion: LayoutExpansion::new(false, false), @@ -123,34 +118,33 @@ impl LayoutSpace { /// A sequence of layouting commands. pub type Commands<'a> = Vec>; -/// Commands issued to the layouting engine by trees. +/// Commands executable by the layouting engine. #[derive(Debug, Clone)] pub enum Command<'a> { /// Layout the given tree in the current context (i.e. not nested). The /// content of the tree is not laid out into a separate box and then added, - /// but simply laid out flat in the active layouting process. + /// but simply laid out flatly 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. + /// text while keeping it part of the current paragraph. LayoutSyntaxTree(&'a SyntaxTree), - /// Add a already computed layout. + /// Add a finished layout. Add(BoxLayout), /// Add multiple layouts, one after another. This is equivalent to multiple - /// [Add](Command::Add) commands. + /// `Add` commands. AddMultiple(MultiLayout), - /// Add spacing of given [kind](super::SpacingKind) along the primary or - /// secondary axis. The spacing kind defines how the spacing interacts with - /// surrounding spacing. + /// Add spacing of the given kind along the primary or secondary axis. The + /// kind defines how the spacing interacts with surrounding spacing. AddSpacing(f64, SpacingKind, GenAxis), /// Start a new line. BreakLine, /// Start a new paragraph. BreakParagraph, - /// Start a new page, which will exist in the finished layout even if it + /// Start a new page, which will be part of the finished layout even if it /// stays empty (since the page break is a _hard_ space break). BreakPage, @@ -162,6 +156,6 @@ pub enum Command<'a> { /// Update the alignment for future boxes added to this layouting process. SetAlignment(LayoutAlign), /// Update the layouting axes along which future boxes will be laid - /// out. This finishes the current line. + /// out. This ends the current line. SetAxes(LayoutAxes), } diff --git a/src/layout/primitive.rs b/src/layout/primitive.rs index 2eb5669b3..1d79f5302 100644 --- a/src/layout/primitive.rs +++ b/src/layout/primitive.rs @@ -1,29 +1,27 @@ //! Layouting primitives. use std::fmt::{self, Display, Formatter}; + use super::prelude::*; -/// Specifies along which axes content is laid out. +/// Specifies the axes along content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAxes { - /// The primary layouting direction. pub primary: Dir, - /// The secondary layouting direction. pub secondary: Dir, } impl LayoutAxes { - /// Create a new instance from the two values. + /// Create a new instance from the two directions. /// /// # Panics - /// This function panics if the axes are aligned, that is, they are + /// This function panics if the directions are aligned, i.e. if they are /// on the same axis. - pub fn new(primary: Dir, secondary: Dir) -> LayoutAxes { + pub fn new(primary: Dir, secondary: Dir) -> Self { if primary.axis() == secondary.axis() { - panic!("invalid aligned axes {} and {}", primary, secondary); + panic!("directions {} and {} are aligned", primary, secondary); } - - LayoutAxes { primary, secondary } + Self { primary, secondary } } /// Return the direction of the specified generic axis. @@ -46,9 +44,13 @@ impl LayoutAxes { /// Directions along which content is laid out. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum Dir { - LTT, + /// Left to right. + LTR, + /// Right to left. RTL, + /// Top to bottom. TTB, + /// Bottom to top. BTT, } @@ -56,34 +58,34 @@ impl Dir { /// The specific axis this direction belongs to. pub fn axis(self) -> SpecAxis { match self { - LTT | RTL => Horizontal, + LTR | RTL => Horizontal, TTB | BTT => Vertical, } } - /// Whether this axis points into the positive coordinate direction. + /// Whether this direction points into the positive coordinate direction. /// - /// The positive axes are left-to-right and top-to-bottom. + /// The positive directions are left-to-right and top-to-bottom. pub fn is_positive(self) -> bool { match self { - LTT | TTB => true, + LTR | TTB => true, RTL | BTT => false, } } /// The factor for this direction. /// - /// - `1` if the direction is positive. - /// - `-1` if the direction is negative. + /// - `1.0` if the direction is positive. + /// - `-1.0` if the direction is negative. pub fn factor(self) -> f64 { if self.is_positive() { 1.0 } else { -1.0 } } - /// The inverse axis. - pub fn inv(self) -> Dir { + /// The inverse direction. + pub fn inv(self) -> Self { match self { - LTT => RTL, - RTL => LTT, + LTR => RTL, + RTL => LTR, TTB => BTT, BTT => TTB, } @@ -93,7 +95,7 @@ impl Dir { impl Display for Dir { fn fmt(&self, f: &mut Formatter) -> fmt::Result { f.pad(match self { - LTT => "ltr", + LTR => "ltr", RTL => "rtl", TTB => "ttb", BTT => "btt", @@ -104,9 +106,9 @@ impl Display for Dir { /// The two generic layouting axes. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub enum GenAxis { - /// The primary axis along which words are laid out. + /// The primary layouting direction along which text and lines flow. Primary, - /// The secondary axis along which lines and paragraphs are laid out. + /// The secondary layouting direction along which paragraphs grow. Secondary, } @@ -154,19 +156,17 @@ impl Display for SpecAxis { /// Specifies where to align a layout in a parent container. #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] pub struct LayoutAlign { - /// The alignment along the primary axis. pub primary: GenAlign, - /// The alignment along the secondary axis. pub secondary: GenAlign, } impl LayoutAlign { - /// Create a new instance from the two values. - pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign { - LayoutAlign { primary, secondary } + /// Create a new instance from the two alignments. + pub fn new(primary: GenAlign, secondary: GenAlign) -> Self { + Self { primary, secondary } } - /// Return the alignment of the specified generic axis. + /// Return the alignment for the specified generic axis. pub fn get(self, axis: GenAxis) -> GenAlign { match axis { Primary => self.primary, @@ -174,7 +174,7 @@ impl LayoutAlign { } } - /// Borrow the alignment of the specified generic axis mutably. + /// Borrow the alignment for the specified generic axis mutably. pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign { match axis { Primary => &mut self.primary, @@ -183,7 +183,7 @@ impl LayoutAlign { } } -/// Where to align content along a generic context. +/// Where to align content along an axis in a generic context. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum GenAlign { Start, @@ -193,7 +193,7 @@ pub enum GenAlign { impl GenAlign { /// The inverse alignment. - pub fn inv(self) -> GenAlign { + pub fn inv(self) -> Self { match self { Start => End, Center => Center, @@ -212,7 +212,7 @@ impl Display for GenAlign { } } -/// Where to align content in a specific context. +/// Where to align content along an axis in a specific context. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum SpecAlign { Left, @@ -225,7 +225,7 @@ pub enum SpecAlign { impl SpecAlign { /// The specific axis this alignment refers to. /// - /// Returns `None` if this is center. + /// Returns `None` if this is `Center` since the axis is unknown. pub fn axis(self) -> Option { match self { Self::Left => Some(Horizontal), @@ -236,7 +236,7 @@ impl SpecAlign { } } - /// Convert this to a generic alignment. + /// The generic version of this alignment in the given system of axes. pub fn to_generic(self, axes: LayoutAxes) -> GenAlign { let get = |spec: SpecAxis, align: GenAlign| { let axis = spec.to_generic(axes); @@ -277,8 +277,8 @@ pub struct LayoutExpansion { impl LayoutExpansion { /// Create a new instance from the two values. - pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { - LayoutExpansion { horizontal, vertical } + pub fn new(horizontal: bool, vertical: bool) -> Self { + Self { horizontal, vertical } } /// Return the expansion value for the given specific axis. @@ -298,8 +298,7 @@ impl LayoutExpansion { } } -/// Defines how a given spacing interacts with (possibly existing) surrounding -/// spacing. +/// Defines how spacing interacts with 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 @@ -317,31 +316,31 @@ pub enum SpacingKind { impl SpacingKind { /// The standard spacing kind used for paragraph spacing. - pub const PARAGRAPH: SpacingKind = SpacingKind::Soft(1); + pub const PARAGRAPH: Self = Self::Soft(1); /// The standard spacing kind used for line spacing. - pub const LINE: SpacingKind = SpacingKind::Soft(2); + pub const LINE: Self = Self::Soft(2); /// The standard spacing kind used for word spacing. - pub const WORD: SpacingKind = SpacingKind::Soft(1); + pub const WORD: Self = Self::Soft(1); } /// 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`. +/// +/// Since the last inserted item may not be spacing at all, this can be `None`. #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum LastSpacing { /// The last item was hard spacing. Hard, /// The last item was soft spacing with the given width and level. Soft(f64, u32), - /// The last item was not spacing. + /// The last item wasn't spacing. None, } impl LastSpacing { /// The width of the soft space if this is a soft space or zero otherwise. - pub(crate) fn soft_or_zero(self) -> f64 { + pub fn soft_or_zero(self) -> f64 { match self { LastSpacing::Soft(space, _) => space, _ => 0.0, diff --git a/src/layout/stack.rs b/src/layout/stack.rs index 28da74b7b..62f2c9765 100644 --- a/src/layout/stack.rs +++ b/src/layout/stack.rs @@ -1,11 +1,9 @@ -//! The stack layouter arranges boxes along the secondary layouting axis. +//! Arranging boxes into a stack along the secondary 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: +//! Individual layouts can be aligned at `Start`, `Center` or `End` along both +//! axes. These alignments are with respect to the size of the finished layout +//! and not the total usable size. This means that a later layout can have +//! influence on the position of an earlier one. Consider the following example. //! ```typst //! [align: right][A word.] //! [align: left][A sentence with a couple more words.] @@ -25,38 +23,35 @@ use crate::geom::Value4; use super::*; /// Performs the stack layouting. -#[derive(Debug)] pub struct StackLayouter { - /// The context for layouting. ctx: StackContext, - /// The output layouts. layouts: MultiLayout, - /// The currently active layout space. + /// The in-progress space. space: Space, } /// The context for stack layouting. #[derive(Debug, Clone)] pub struct StackContext { - /// The spaces to layout in. + /// The spaces to layout into. pub spaces: LayoutSpaces, - /// The initial layouting axes, which can be updated by the - /// [`StackLayouter::set_axes`] method. + /// The initial layouting axes, which can be updated through `set_axes`. pub axes: LayoutAxes, - /// Which alignment to set on the resulting layout. This affects how it will - /// be positioned in a parent box. + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, - /// Whether to have repeated spaces or to use only the first and only once. + /// Whether to spill over into copies of the last space or finish layouting + /// when the last space is used up. pub repeat: bool, } /// A layout space composed of subspaces which can have different axes and /// alignments. -#[derive(Debug)] struct Space { - /// The index of this space in the list of spaces. + /// The index of this space in `ctx.spaces`. index: usize, - /// Whether to add the layout for this space even if it would be empty. + /// Whether to include a layout for this space even if it would be empty. hard: bool, /// The so-far accumulated layouts. layouts: Vec<(LayoutAxes, BoxLayout)>, @@ -66,18 +61,20 @@ struct Space { usable: Size, /// The specialized extra-needed size to affect the size at all. extra: Size, - /// The rulers of a space dictate which alignments for new boxes are still - /// allowed and which require a new space to be started. + /// Dictate which alignments for new boxes are still allowed and which + /// require a new space to be started. For example, after an `End`-aligned + /// item, no `Start`-aligned one can follow. rulers: Value4, - /// The last added spacing if the last added thing was spacing. + /// The spacing state. This influences how new spacing is handled, e.g. hard + /// spacing may override soft spacing. last_spacing: LastSpacing, } impl StackLayouter { /// Create a new stack layouter. - pub fn new(ctx: StackContext) -> StackLayouter { + pub fn new(ctx: StackContext) -> Self { let space = ctx.spaces[0]; - StackLayouter { + Self { ctx, layouts: MultiLayout::new(), space: Space::new(0, true, space.usable()), @@ -87,8 +84,8 @@ impl StackLayouter { /// Add a layout to the stack. pub fn add(&mut self, layout: BoxLayout) { // If the alignment cannot be fitted in this space, finish it. - // TODO: Issue warning for non-fitting alignment in - // non-repeating context. + // TODO: Issue warning for non-fitting alignment in non-repeating + // context. if !self.update_rulers(layout.align) && self.ctx.repeat { self.finish_space(true); } @@ -116,14 +113,14 @@ impl StackLayouter { /// Add multiple layouts to the stack. /// - /// This function simply calls `add` repeatedly for each layout. + /// This is equivalent to calling `add` repeatedly for each layout. pub fn add_multiple(&mut self, layouts: MultiLayout) { for layout in layouts { self.add(layout); } } - /// Add secondary spacing to the stack. + /// Add spacing to the stack. pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) { match kind { // A hard space is simply an empty box. @@ -133,11 +130,14 @@ impl StackLayouter { let size = Size::with_y(spacing); self.update_metrics(size); - self.space.layouts.push((self.ctx.axes, BoxLayout { - size: size.specialized(self.ctx.axes), - align: LayoutAlign::new(Start, Start), - elements: LayoutElements::new(), - })); + self.space.layouts.push(( + self.ctx.axes, + BoxLayout { + size: size.specialized(self.ctx.axes), + align: LayoutAlign::new(Start, Start), + elements: LayoutElements::new(), + } + )); self.space.last_spacing = LastSpacing::Hard; } @@ -158,8 +158,6 @@ impl StackLayouter { } } - /// Update the size metrics to reflect that a layout or spacing with the - /// given generalized size has been added. fn update_metrics(&mut self, added: Size) { let axes = self.ctx.axes; @@ -177,31 +175,29 @@ impl StackLayouter { *self.space.usable.secondary_mut(axes) -= added.y; } - /// Update the rulers to account for the new layout. Returns true if a - /// space break is necessary. + /// Returns true if a space break is necessary. fn update_rulers(&mut self, align: LayoutAlign) -> bool { let allowed = self.is_fitting_alignment(align); if allowed { - *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) - = align.secondary; + *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) = + align.secondary; } allowed } - /// Whether a layout with the given alignment can still be layouted in the - /// active space. - pub fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { + /// Whether a layout with the given alignment can still be layouted into the + /// active space or a space break is necessary. + pub(crate) fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { self.is_fitting_axis(self.ctx.axes.primary, align.primary) && self.is_fitting_axis(self.ctx.axes.secondary, align.secondary) } - /// Whether the given alignment is still allowed according to the rulers. fn is_fitting_axis(&mut self, dir: Dir, align: GenAlign) -> bool { align >= *self.space.rulers.get_mut(dir, Start) - && align <= self.space.rulers.get_mut(dir, End).inv() + && align <= self.space.rulers.get_mut(dir, End).inv() } - /// Change the layouting axes used by this layouter. + /// Update the layouting axes. pub fn set_axes(&mut self, axes: LayoutAxes) { // Forget the spacing because it is not relevant anymore. if axes.secondary != self.ctx.axes.secondary { @@ -211,10 +207,10 @@ impl StackLayouter { self.ctx.axes = axes; } - /// Change the layouting spaces to use. + /// Update the layouting spaces. /// /// 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 out into it yet. Otherwise, the followup spaces are /// replaced. pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { if replace_empty && self.space_is_empty() { @@ -234,13 +230,13 @@ impl StackLayouter { if space.usable().fits(size) { self.finish_space(true); self.start_space(start + index, true); - return; + break; } } } - /// The remaining unpadded, unexpanding spaces. If a function is laid out - /// into these spaces, it will fit into this stack. + /// The remaining inner spaces. If something is laid out into these spaces, + /// it will fit into this stack. pub fn remaining(&self) -> LayoutSpaces { let size = self.usable(); @@ -251,7 +247,7 @@ impl StackLayouter { }]; for space in &self.ctx.spaces[self.next_space()..] { - spaces.push(space.usable_space()); + spaces.push(space.inner()); } spaces @@ -264,17 +260,17 @@ impl StackLayouter { .specialized(self.ctx.axes) } - /// Whether the current layout space (not subspace) is empty. + /// Whether the current layout space is empty. pub fn space_is_empty(&self) -> bool { self.space.size == Size::ZERO && self.space.layouts.is_empty() } - /// Whether the current layout space is the last is the followup list. + /// Whether the current layout space is the last in the followup list. pub fn space_is_last(&self) -> bool { self.space.index == self.ctx.spaces.len() - 1 } - /// Compute the finished list of boxes. + /// Finish everything up and return the final collection of boxes. pub fn finish(mut self) -> MultiLayout { if self.space.hard || !self.space_is_empty() { self.finish_space(false); @@ -282,7 +278,7 @@ impl StackLayouter { self.layouts } - /// Finish the current space and start a new one. + /// Finish active current space and start a new one. pub fn finish_space(&mut self, hard: bool) { let space = self.ctx.spaces[self.space.index]; @@ -322,8 +318,8 @@ impl StackLayouter { // layout uses up space from the origin to the end. Thus, it reduces // the usable space for following layouts at it's origin by its // extent along the secondary axis. - *bound.get_mut(axes.secondary, Start) - += axes.secondary.factor() * layout.size.secondary(*axes); + *bound.get_mut(axes.secondary, Start) += + axes.secondary.factor() * layout.size.secondary(*axes); } // ------------------------------------------------------------------ // @@ -351,8 +347,8 @@ impl StackLayouter { // We reduce the bounding box of this layout at it's end by the // accumulated secondary extent of all layouts we have seen so far, // which are the layouts after this one since we iterate reversed. - *bound.get_mut(axes.secondary, End) - -= axes.secondary.factor() * extent.y; + *bound.get_mut(axes.secondary, End) -= + axes.secondary.factor() * extent.y; // Then, we add this layout's secondary extent to the accumulator. let size = layout.size.generalized(*axes); @@ -395,21 +391,19 @@ impl StackLayouter { self.start_space(self.next_space(), hard) } - /// Start a new space with the given index. fn start_space(&mut self, index: usize, hard: bool) { let space = self.ctx.spaces[index]; self.space = Space::new(index, hard, space.usable()); } - /// The index of the next space. fn next_space(&self) -> usize { (self.space.index + 1).min(self.ctx.spaces.len() - 1) } } impl Space { - fn new(index: usize, hard: bool, usable: Size) -> Space { - Space { + fn new(index: usize, hard: bool, usable: Size) -> Self { + Self { index, hard, layouts: vec![], diff --git a/src/layout/text.rs b/src/layout/text.rs index 5c18cd328..b95110b73 100644 --- a/src/layout/text.rs +++ b/src/layout/text.rs @@ -1,17 +1,23 @@ -//! The text layouter layouts continous pieces of text into boxes. +//! Layouting of 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 ttf_parser::GlyphId; use fontdock::{FaceId, FaceQuery, FontStyle}; +use ttf_parser::GlyphId; + use crate::font::SharedFontLoader; use crate::geom::Size; use crate::style::TextStyle; use super::elements::{LayoutElement, Shaped}; use super::*; +/// Layouts text into a box. +pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout { + TextLayouter::new(text, ctx).layout().await +} + /// Performs the text layouting. #[derive(Debug)] struct TextLayouter<'a> { @@ -26,28 +32,24 @@ struct TextLayouter<'a> { /// The context for text layouting. #[derive(Debug, Copy, Clone)] pub struct TextContext<'a> { - /// The font loader to retrieve fonts from when typesetting text - /// using [`layout_text`]. + /// The font loader to retrieve fonts from when typesetting text with + /// `layout_text`. pub loader: &'a SharedFontLoader, /// The style for text: Font selection with classes, weights and variants, /// font sizes, spacing and so on. pub style: &'a TextStyle, - /// The axes along which the word is laid out. For now, only - /// primary-horizontal layouting is supported. - pub axes: LayoutAxes, - /// The alignment of the finished layout. + /// The direction into which the word is laid out. For now, only horizontal + /// directions are supported. + pub dir: Dir, + /// The alignment of the _resulting_ layout. This does not effect the line + /// layouting itself, but rather how the finished layout will be positioned + /// in a parent layout. pub align: LayoutAlign, } -/// Layouts text into a box. -pub async fn layout_text(text: &str, ctx: TextContext<'_>) -> BoxLayout { - TextLayouter::new(text, ctx).layout().await -} - impl<'a> TextLayouter<'a> { - /// Create a new text layouter. - fn new(text: &'a str, ctx: TextContext<'a>) -> TextLayouter<'a> { - TextLayouter { + fn new(text: &'a str, ctx: TextContext<'a>) -> Self { + Self { ctx, text, shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()), @@ -57,10 +59,9 @@ impl<'a> TextLayouter<'a> { } } - /// Do the layouting. async fn layout(mut self) -> BoxLayout { // If the primary axis is negative, we layout the characters reversed. - if self.ctx.axes.primary.is_positive() { + if self.ctx.dir.is_positive() { for c in self.text.chars() { self.layout_char(c).await; } @@ -83,7 +84,6 @@ impl<'a> TextLayouter<'a> { } } - /// Layout an individual character. async fn layout_char(&mut self, c: char) { let (index, glyph, char_width) = match self.select_font(c).await { Some(selected) => selected, @@ -115,11 +115,8 @@ impl<'a> TextLayouter<'a> { self.width += char_width; } - /// Select the best font for a character and return its index along with - /// the width of the char in the font. async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> { let mut loader = self.ctx.loader.borrow_mut(); - let mut variant = self.ctx.style.variant; if self.ctx.style.bolder { diff --git a/src/layout/tree.rs b/src/layout/tree.rs index 44c592115..d20fc666c 100644 --- a/src/layout/tree.rs +++ b/src/layout/tree.rs @@ -1,19 +1,26 @@ -//! The tree layouter layouts trees (i.e. -//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func)) -//! by executing commands issued by the trees. +//! Layouting of syntax trees. -use crate::{Pass, Feedback, DynFuture}; use crate::style::LayoutStyle; use crate::syntax::decoration::Decoration; -use crate::syntax::tree::{SyntaxTree, SyntaxNode, DynamicNode}; use crate::syntax::span::{Span, Spanned}; -use super::line::{LineLayouter, LineContext}; +use crate::syntax::tree::{DynamicNode, SyntaxNode, SyntaxTree}; +use crate::{DynFuture, Feedback, Pass}; +use super::line::{LineContext, LineLayouter}; use super::text::{layout_text, TextContext}; use super::*; +/// Layout a syntax tree into a collection of boxes. +pub async fn layout_tree( + tree: &SyntaxTree, + ctx: LayoutContext<'_>, +) -> Pass { + let mut layouter = TreeLayouter::new(ctx); + layouter.layout_tree(tree).await; + layouter.finish() +} + /// Performs the tree layouting. -#[derive(Debug)] -pub struct TreeLayouter<'a> { +struct TreeLayouter<'a> { ctx: LayoutContext<'a>, layouter: LineLayouter, style: LayoutStyle, @@ -21,9 +28,8 @@ pub struct TreeLayouter<'a> { } impl<'a> TreeLayouter<'a> { - /// Create a new tree layouter. - pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> { - TreeLayouter { + fn new(ctx: LayoutContext<'a>) -> Self { + Self { layouter: LineLayouter::new(LineContext { spaces: ctx.spaces.clone(), axes: ctx.axes, @@ -37,15 +43,17 @@ impl<'a> TreeLayouter<'a> { } } - /// Layout a syntax tree by directly processing the nodes instead of using - /// the command based architecture. - pub async fn layout_tree(&mut self, tree: &SyntaxTree) { + async fn layout_tree(&mut self, tree: &SyntaxTree) { for node in tree { self.layout_node(node).await; } } - pub async fn layout_node(&mut self, node: &Spanned) { + fn finish(self) -> Pass { + Pass::new(self.layouter.finish(), self.feedback) + } + + async fn layout_node(&mut self, node: &Spanned) { let decorate = |this: &mut TreeLayouter, deco| { this.feedback.decorations.push(Spanned::new(deco, node.span)); }; @@ -80,7 +88,10 @@ impl<'a> TreeLayouter<'a> { SyntaxNode::Raw(lines) => { // TODO: Make this more efficient. let fallback = self.style.text.fallback.clone(); - self.style.text.fallback.list_mut().insert(0, "monospace".to_string()); + self.style.text.fallback + .list_mut() + .insert(0, "monospace".to_string()); + self.style.text.fallback.flatten(); // Layout the first line. @@ -104,17 +115,15 @@ impl<'a> TreeLayouter<'a> { } } - /// Layout a node into this layouting process. - pub async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) { - // Execute the tree's layout function which generates the commands. + async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) { + // Execute the tree's command-generating layout function. let layouted = dynamic.v.layout(LayoutContext { style: &self.style, spaces: self.layouter.remaining(), - nested: true, - .. self.ctx + root: true, + ..self.ctx }).await; - // Add the errors generated by the tree to the error list. self.feedback.extend_offset(layouted.feedback, dynamic.span.start); for command in layouted.output { @@ -122,13 +131,6 @@ impl<'a> TreeLayouter<'a> { } } - /// Compute the finished list of boxes. - pub fn finish(self) -> Pass { - Pass::new(self.layouter.finish(), self.feedback) - } - - /// Execute a command issued by a tree. When the command is errorful, the - /// given span is stored with the error. fn execute_command<'r>( &'r mut self, command: Command<'r>, @@ -149,13 +151,13 @@ impl<'a> TreeLayouter<'a> { BreakLine => self.layouter.finish_line(), BreakParagraph => self.layout_paragraph(), BreakPage => { - if self.ctx.nested { + if self.ctx.root { + self.layouter.finish_space(true) + } else { error!( @self.feedback, tree_span, - "page break cannot be issued from nested context", + "page break cannot only be issued from root context", ); - } else { - self.layouter.finish_space(true) } } @@ -164,12 +166,7 @@ impl<'a> TreeLayouter<'a> { self.style.text = style; } SetPageStyle(style) => { - if self.ctx.nested { - error!( - @self.feedback, tree_span, - "page style cannot be changed from nested context", - ); - } else { + if self.ctx.root { self.style.page = style; // The line layouter has no idea of page styles and thus we @@ -184,6 +181,11 @@ impl<'a> TreeLayouter<'a> { expansion: LayoutExpansion::new(true, true), } ], true); + } else { + error!( + @self.feedback, tree_span, + "page style cannot only be changed from root context", + ); } } @@ -195,17 +197,20 @@ impl<'a> TreeLayouter<'a> { } }) } - /// Layout a continous piece of text and add it to the line layouter. async fn layout_text(&mut self, text: &str) { - self.layouter.add(layout_text(text, TextContext { - loader: &self.ctx.loader, - style: &self.style.text, - axes: self.ctx.axes, - align: self.ctx.align, - }).await) + self.layouter.add( + layout_text( + text, + TextContext { + loader: &self.ctx.loader, + style: &self.style.text, + dir: self.ctx.axes.primary, + align: self.ctx.align, + } + ).await + ); } - /// Add the spacing for a syntactic space node. fn layout_space(&mut self) { self.layouter.add_primary_spacing( self.style.text.word_spacing(), @@ -213,7 +218,6 @@ impl<'a> TreeLayouter<'a> { ); } - /// Finish the paragraph and add paragraph spacing. fn layout_paragraph(&mut self) { self.layouter.add_secondary_spacing( self.style.text.paragraph_spacing(), diff --git a/src/length.rs b/src/length.rs index 8e07a8e64..5634da191 100644 --- a/src/length.rs +++ b/src/length.rs @@ -84,7 +84,7 @@ impl Length { } /// Convert this to a length with a different unit. - pub fn with_unit(self, unit: Unit) -> Length { + pub fn with_unit(self, unit: Unit) -> Self { Self { val: self.val * self.unit.raw_scale() / unit.raw_scale(), unit, @@ -160,7 +160,7 @@ impl FromStr for Length { src[..split] .parse::() - .map(|val| Length::new(val, unit)) + .map(|val| Self::new(val, unit)) .map_err(|_| ParseLengthError) } } diff --git a/src/lib.rs b/src/lib.rs index 5a0b8d0be..fa6300bb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,18 +2,40 @@ //! //! # Steps //! - **Parsing:** The parsing step first transforms a plain string into an -//! [iterator of tokens](crate::syntax::Tokens). Then, a parser constructs a -//! syntax tree from the token stream. The structures describing the tree can -//! be found in the [syntax](crate::syntax) module. +//! [iterator of tokens][tokens]. Then, a parser constructs a syntax tree from +//! the token stream. The structures describing the tree can be found in the +//! [syntax] module. //! - **Layouting:** The next step is to transform the syntax tree into a //! portable representation of the typesetted document. Types for these can be -//! found in the [layout](crate::layout) module. A finished layout reading for -//! exporting is a [MultiLayout](crate::layout::MultiLayout) consisting of -//! multiple boxes (or pages). +//! found in the [layout] module. A finished layout ready for exporting is a +//! [`MultiLayout`] consisting of multiple boxes (or pages). //! - **Exporting:** The finished layout can then be exported into a supported -//! format. Submodules for these formats are located in the -//! [export](crate::export) module. Currently, the only supported output -//! format is [_PDF_](crate::export::pdf). +//! format. Submodules for these formats are located in the [export] module. +//! Currently, the only supported output format is [_PDF_]. +//! +//! [tokens]: syntax/tokens/struct.Tokens.html +//! [syntax]: syntax/index.html +//! [layout]: layout/index.html +//! [export]: export/index.html +//! [_PDF_]: export/pdf/index.html +//! [`MultiLayout`]: layout/type.MultiLayout.html + +#[macro_use] +mod macros; +#[macro_use] +pub mod diagnostic; +#[macro_use] +pub mod func; + +pub mod export; +pub mod font; +pub mod geom; +pub mod layout; +pub mod length; +pub mod library; +pub mod paper; +pub mod style; +pub mod syntax; use std::fmt::Debug; use std::future::Future; @@ -24,26 +46,9 @@ use crate::font::SharedFontLoader; use crate::layout::MultiLayout; use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::syntax::decoration::Decorations; -use crate::syntax::tree::SyntaxTree; use crate::syntax::parsing::{parse, ParseState}; -use crate::syntax::scope::Scope; use crate::syntax::span::{Offset, Pos}; - -#[macro_use] -mod macros; -#[macro_use] -pub mod diagnostic; -pub mod export; -pub mod font; -#[macro_use] -pub mod func; -pub mod geom; -pub mod layout; -pub mod library; -pub mod length; -pub mod paper; -pub mod style; -pub mod syntax; +use crate::syntax::tree::SyntaxTree; /// Transforms source code into typesetted layouts. /// @@ -59,11 +64,11 @@ pub struct Typesetter { impl Typesetter { /// Create a new typesetter. - pub fn new(loader: SharedFontLoader) -> Typesetter { - Typesetter { + pub fn new(loader: SharedFontLoader) -> Self { + Self { loader, style: LayoutStyle::default(), - parse_state: ParseState { scope: Scope::with_std() }, + parse_state: ParseState { scope: crate::library::std() }, } } @@ -100,9 +105,9 @@ impl Typesetter { expansion: LayoutExpansion::new(true, true), }], repeat: true, - axes: LayoutAxes::new(LTT, TTB), + axes: LayoutAxes::new(LTR, TTB), align: LayoutAlign::new(Start, Start), - nested: false, + root: true, }, ).await } @@ -119,7 +124,7 @@ impl Typesetter { /// A dynamic future type which allows recursive invocation of async functions /// when used as the return type. This is also how the async trait functions /// work internally. -pub type DynFuture<'a, T> = Pin + 'a>>; +pub type DynFuture<'a, T> = Pin + 'a>>; /// The result of some pass: Some output `T` and feedback data. #[derive(Debug, Clone, Eq, PartialEq)] @@ -132,12 +137,12 @@ pub struct Pass { impl Pass { /// Create a new pass from output and feedback data. - pub fn new(output: T, feedback: Feedback) -> Pass { - Pass { output, feedback } + pub fn new(output: T, feedback: Feedback) -> Self { + Self { output, feedback } } /// Map the output type and keep the feedback data. - pub fn map(self, f: F) -> Pass where F: FnOnce(T) -> U { + pub fn map(self, f: impl FnOnce(T) -> U) -> Pass { Pass { output: f(self.output), feedback: self.feedback, @@ -145,10 +150,10 @@ impl Pass { } } -/// User feedback data accumulated during a compilation pass. +/// Diagnostic and semantic syntax highlighting data. #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct Feedback { - /// Diagnostics in the source code. + /// Diagnostics about the source code. pub diagnostics: Diagnostics, /// Decorations of the source code for semantic syntax highlighting. pub decorations: Decorations, @@ -156,28 +161,28 @@ pub struct Feedback { impl Feedback { /// Create a new feedback instance without errors and decos. - pub fn new() -> Feedback { - Feedback { + pub fn new() -> Self { + Self { diagnostics: vec![], decorations: vec![], } } /// Merged two feedbacks into one. - pub fn merge(mut a: Feedback, b: Feedback) -> Feedback { + pub fn merge(mut a: Self, b: Self) -> Self { a.extend(b); a } /// Add other feedback data to this feedback. - pub fn extend(&mut self, other: Feedback) { - self.diagnostics.extend(other.diagnostics); - self.decorations.extend(other.decorations); + pub fn extend(&mut self, more: Self) { + self.diagnostics.extend(more.diagnostics); + self.decorations.extend(more.decorations); } /// Add more feedback whose spans are local and need to be offset by an /// `offset` to be correct in this feedback's context. - pub fn extend_offset(&mut self, more: Feedback, offset: Pos) { + pub fn extend_offset(&mut self, more: Self, offset: Pos) { self.diagnostics.extend(more.diagnostics.offset(offset)); self.decorations.extend(more.decorations.offset(offset)); } diff --git a/src/library/font.rs b/src/library/font.rs index 21ac14c75..6e7110212 100644 --- a/src/library/font.rs +++ b/src/library/font.rs @@ -1,6 +1,5 @@ -//! Font configuration. - use fontdock::{FontStyle, FontWeight, FontWidth}; + use crate::length::ScaleLength; use super::*; @@ -19,7 +18,6 @@ function! { parse(header, body, state, f) { let size = header.args.pos.get::(); - let style = header.args.key.get::("style", f); let weight = header.args.key.get::("weight", f); let width = header.args.key.get::("width", f); @@ -40,7 +38,7 @@ function! { }) .collect(); - FontFunc { + Self { body: parse_maybe_body(body, state, f), size, style, @@ -52,30 +50,39 @@ function! { } layout(self, ctx, f) { - styled(&self.body, ctx, Some(()), |t, _| { - self.size.with(|s| match s { - ScaleLength::Absolute(length) => { - t.base_font_size = length.as_raw(); - t.font_scale = 1.0; - } - ScaleLength::Scaled(scale) => t.font_scale = scale, - }); + let mut text = ctx.style.text.clone(); - self.style.with(|s| t.variant.style = s); - self.weight.with(|w| t.variant.weight = w); - self.width.with(|w| t.variant.width = w); - - if !self.list.is_empty() { - *t.fallback.list_mut() = self.list.iter() - .map(|s| s.to_lowercase()) - .collect(); + self.size.with(|s| match s { + ScaleLength::Absolute(length) => { + text.base_font_size = length.as_raw(); + text.font_scale = 1.0; } + ScaleLength::Scaled(scale) => text.font_scale = scale, + }); - for (class, fallback) in &self.classes { - t.fallback.set_class_list(class.clone(), fallback.clone()); - } + self.style.with(|s| text.variant.style = s); + self.weight.with(|w| text.variant.weight = w); + self.width.with(|w| text.variant.width = w); - t.fallback.flatten(); - }) + if !self.list.is_empty() { + *text.fallback.list_mut() = self.list.iter() + .map(|s| s.to_lowercase()) + .collect(); + } + + for (class, fallback) in &self.classes { + text.fallback.set_class_list(class.clone(), fallback.clone()); + } + + text.fallback.flatten(); + + match &self.body { + Some(tree) => vec![ + SetTextStyle(text), + LayoutSyntaxTree(tree), + SetTextStyle(ctx.style.text.clone()), + ], + None => vec![SetTextStyle(text)], + } } } diff --git a/src/library/layout.rs b/src/library/layout.rs index d6d024363..f3ddaadfd 100644 --- a/src/library/layout.rs +++ b/src/library/layout.rs @@ -1,5 +1,3 @@ -//! Layout building blocks. - use crate::length::ScaleLength; use super::*; @@ -13,7 +11,7 @@ function! { } parse(header, body, state, f) { - BoxFunc { + Self { body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()), width: header.args.key.get::("width", f), height: header.args.key.get::("height", f), @@ -21,8 +19,8 @@ function! { } layout(self, ctx, f) { - ctx.repeat = false; ctx.spaces.truncate(1); + ctx.repeat = false; self.width.with(|v| { let length = v.raw_scaled(ctx.base.x); @@ -51,13 +49,13 @@ function! { #[derive(Debug, Clone, PartialEq)] pub struct AlignFunc { body: Option, - aligns: Vec>, + aligns: SpanVec, h: Option>, v: Option>, } parse(header, body, state, f) { - AlignFunc { + Self { body: parse_maybe_body(body, state, f), aligns: header.args.pos.all::>().collect(), h: header.args.key.get::>("horizontal", f), diff --git a/src/library/mod.rs b/src/library/mod.rs index 7240e42bb..0f61b901c 100644 --- a/src/library/mod.rs +++ b/src/library/mod.rs @@ -1,16 +1,19 @@ //! The standard library. +mod font; +mod layout; +mod page; +mod spacing; + +pub use font::*; +pub use layout::*; +pub use page::*; +pub use spacing::*; + use crate::func::prelude::*; -use crate::layout::{LayoutContext, Commands}; use crate::syntax::scope::Scope; -macro_rules! lib { ($name:ident) => { mod $name; pub use $name::*; }} -lib!(font); -lib!(layout); -lib!(page); -lib!(spacing); - -/// Create a scope with all standard functions. +/// Create a scope with all standard library functions. pub fn std() -> Scope { let mut std = Scope::new::(); @@ -28,7 +31,10 @@ pub fn std() -> Scope { } function! { - /// `val`: Layouts the body with no special effect. + /// `val`: Ignores all arguments and layouts the body flatly. + /// + /// This is also the fallback function, which is used when a function name + /// could not be resolved. #[derive(Debug, Clone, PartialEq)] pub struct ValFunc { body: Option, @@ -37,7 +43,7 @@ function! { parse(header, body, state, f) { header.args.pos.0.clear(); header.args.key.0.clear(); - ValFunc { body: parse_maybe_body(body, state, f), } + Self { body: parse_maybe_body(body, state, f), } } layout(self, ctx, f) { @@ -47,27 +53,3 @@ function! { } } } - -/// Layout an optional body with a change of the text style. -fn styled<'a, T, F>( - body: &'a Option, - ctx: LayoutContext<'_>, - data: Option, - f: F, -) -> Commands<'a> where F: FnOnce(&mut TextStyle, T) { - if let Some(data) = data { - let mut style = ctx.style.text.clone(); - f(&mut style, data); - - match body { - Some(tree) => vec![ - SetTextStyle(style), - LayoutSyntaxTree(tree), - SetTextStyle(ctx.style.text.clone()), - ], - None => vec![SetTextStyle(style)], - } - } else { - vec![] - } -} diff --git a/src/library/page.rs b/src/library/page.rs index faf08ee04..1cc76d7fa 100644 --- a/src/library/page.rs +++ b/src/library/page.rs @@ -1,5 +1,3 @@ -//! Page setup. - use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass}; use super::*; @@ -21,7 +19,7 @@ function! { parse(header, body, state, f) { expect_no_body(body, f); - PageFunc { + Self { paper: header.args.pos.get::(), width: header.args.key.get::("width", f), height: header.args.key.get::("height", f), diff --git a/src/library/spacing.rs b/src/library/spacing.rs index d8263694a..545f39102 100644 --- a/src/library/spacing.rs +++ b/src/library/spacing.rs @@ -1,13 +1,11 @@ -//! Spacing. - -use crate::length::ScaleLength; use crate::layout::SpacingKind; +use crate::length::ScaleLength; use super::*; function! { /// `parbreak`: Ends the current paragraph. /// - /// self has the same effect as two subsequent newlines. + /// This has the same effect as two subsequent newlines. #[derive(Debug, Default, Clone, PartialEq)] pub struct ParBreakFunc; @@ -25,7 +23,7 @@ function! { } function! { - /// `h` and `v`: Add spacing along an axis. + /// `h` and `v`: Add horizontal or vertical spacing. #[derive(Debug, Clone, PartialEq)] pub struct SpacingFunc { spacing: Option<(SpecAxis, ScaleLength)>, @@ -35,7 +33,7 @@ function! { parse(header, body, state, f, meta) { expect_no_body(body, f); - SpacingFunc { + Self { spacing: header.args.pos.expect::(f) .map(|s| (meta, s)) .or_missing(header.name.span, "spacing", f), diff --git a/src/macros.rs b/src/macros.rs index 8ba326503..2236c4806 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -9,7 +9,7 @@ macro_rules! try_or { ($result:expr, $or:expr $(,)?) => { match $result { Ok(v) => v, - Err(_) => { $or } + Err(_) => $or, } }; } @@ -19,7 +19,7 @@ macro_rules! try_opt_or { ($option:expr, $or:expr $(,)?) => { match $option { Some(v) => v, - None => { $or } + None => $or, } }; } diff --git a/src/paper.rs b/src/paper.rs index e8913d120..97f767037 100644 --- a/src/paper.rs +++ b/src/paper.rs @@ -16,7 +16,7 @@ pub struct Paper { impl Paper { /// The paper with the given name. - pub fn from_name(name: &str) -> Option { + pub fn from_name(name: &str) -> Option { parse_paper(name) } @@ -39,7 +39,6 @@ pub enum PaperClass { impl PaperClass { /// The default margins for this page class. pub fn default_margins(self) -> Value4 { - use PaperClass::*; let values = |l, t, r, b| Value4::new( ScaleLength::Scaled(l), ScaleLength::Scaled(t), @@ -48,11 +47,11 @@ impl PaperClass { ); match self { - Custom => values(0.1190, 0.0842, 0.1190, 0.0842), - Base => values(0.1190, 0.0842, 0.1190, 0.0842), - US => values(0.1760, 0.1092, 0.1760, 0.0910), - Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294), - Book => values(0.1200, 0.0852, 0.1500, 0.0965), + Self::Custom => values(0.1190, 0.0842, 0.1190, 0.0842), + Self::Base => values(0.1190, 0.0842, 0.1190, 0.0842), + Self::US => values(0.1760, 0.1092, 0.1760, 0.0910), + Self::Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294), + Self::Book => values(0.1200, 0.0852, 0.1500, 0.0965), } } } diff --git a/src/style.rs b/src/style.rs index 9dce381ec..803899197 100644 --- a/src/style.rs +++ b/src/style.rs @@ -1,7 +1,10 @@ //! Styles for text and pages. -use fontdock::{fallback, FallbackTree, FontVariant, FontStyle, FontWeight, FontWidth}; -use crate::geom::{Size, Margins, Value4}; +use fontdock::{ + fallback, FallbackTree, FontStyle, FontVariant, FontWeight, FontWidth, +}; + +use crate::geom::{Margins, Size, Value4}; use crate::length::{Length, ScaleLength}; use crate::paper::{Paper, PaperClass, PAPER_A4}; @@ -62,8 +65,8 @@ impl TextStyle { } impl Default for TextStyle { - fn default() -> TextStyle { - TextStyle { + fn default() -> Self { + Self { fallback: fallback! { list: ["sans-serif"], classes: { @@ -105,8 +108,8 @@ pub struct PageStyle { impl PageStyle { /// The default page style for the given paper. - pub fn new(paper: Paper) -> PageStyle { - PageStyle { + pub fn new(paper: Paper) -> Self { + Self { class: paper.class, size: paper.size(), margins: Value4::with_all(None), @@ -127,7 +130,7 @@ impl PageStyle { } impl Default for PageStyle { - fn default() -> PageStyle { - PageStyle::new(PAPER_A4) + fn default() -> Self { + Self::new(PAPER_A4) } } diff --git a/src/syntax/decoration.rs b/src/syntax/decoration.rs index 13a9ad362..a9097444d 100644 --- a/src/syntax/decoration.rs +++ b/src/syntax/decoration.rs @@ -13,32 +13,16 @@ pub type Decorations = SpanVec; #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] pub enum Decoration { - /// A valid function name. - /// ```typst - /// [box] - /// ^^^ - /// ``` - ValidFuncName, - /// An invalid function name. - /// ```typst - /// [blabla] - /// ^^^^^^ - /// ``` - InvalidFuncName, - /// A key of a keyword argument. - /// ```typst - /// [box: width=5cm] - /// ^^^^^ - /// ``` + /// A valid, successfully resolved function name. + ResolvedFunc, + /// An invalid, unresolved function name. + UnresolvedFunc, + /// A key part of a keyword argument. ArgumentKey, - /// A key in an object. - /// ```typst - /// [box: padding={ left: 1cm, right: 2cm}] - /// ^^^^ ^^^^^ - /// ``` + /// A key part of a pair in an object. ObjectKey, - /// An italic word. + /// Text in italics. Italic, - /// A bold word. + /// Text in bold. Bold, } diff --git a/src/syntax/expr.rs b/src/syntax/expr.rs index 0a9ab1498..a637f115f 100644 --- a/src/syntax/expr.rs +++ b/src/syntax/expr.rs @@ -5,26 +5,26 @@ use std::ops::Deref; use std::str::FromStr; use std::u8; -use crate::Feedback; use crate::length::Length; -use super::span::{Spanned, SpanVec}; +use crate::Feedback; +use super::span::{SpanVec, Spanned}; use super::tokens::is_identifier; use super::value::Value; -/// An argument or return value. +/// An expression. #[derive(Clone, PartialEq)] pub enum Expr { /// An identifier: `ident`. Ident(Ident), /// A string: `"string"`. Str(String), + /// A boolean: `true, false`. + Bool(bool), /// A number: `1.2, 200%`. Number(f64), /// A length: `2cm, 5.2in`. Length(Length), - /// A bool: `true, false`. - Bool(bool), - /// A color value, including the alpha channel: `#f79143ff`. + /// A color value with alpha channel: `#f79143ff`. Color(RgbaColor), /// A tuple: `(false, 12cm, "hi")`. Tuple(Tuple), @@ -32,37 +32,38 @@ pub enum Expr { NamedTuple(NamedTuple), /// An object: `{ fit: false, width: 12pt }`. Object(Object), - /// An operator that negates the contained expression. + /// An operation that negates the contained expression. Neg(Box>), - /// An operator that adds the contained expressions. + /// An operation that adds the contained expressions. Add(Box>, Box>), - /// An operator that subtracts contained expressions. + /// An operation that subtracts the contained expressions. Sub(Box>, Box>), - /// An operator that multiplies the contained expressions. + /// An operation that multiplies the contained expressions. Mul(Box>, Box>), - /// An operator that divides the contained expressions. + /// An operation that divides the contained expressions. Div(Box>, Box>), } impl Expr { - /// A natural-language name of the type of this expression, e.g. "identifier". + /// A natural-language name of the type of this expression, e.g. + /// "identifier". pub fn name(&self) -> &'static str { use Expr::*; match self { - Ident(_) => "identifier", - Str(_) => "string", - Number(_) => "number", - Length(_) => "length", - Bool(_) => "bool", - Color(_) => "color", - Tuple(_) => "tuple", + Ident(_) => "identifier", + Str(_) => "string", + Bool(_) => "bool", + Number(_) => "number", + Length(_) => "length", + Color(_) => "color", + Tuple(_) => "tuple", NamedTuple(_) => "named tuple", - Object(_) => "object", - Neg(_) => "negation", - Add(_, _) => "addition", - Sub(_, _) => "subtraction", - Mul(_, _) => "multiplication", - Div(_, _) => "division", + Object(_) => "object", + Neg(_) => "negation", + Add(_, _) => "addition", + Sub(_, _) => "subtraction", + Mul(_, _) => "multiplication", + Div(_, _) => "division", } } } @@ -73,9 +74,9 @@ impl Debug for Expr { match self { Ident(i) => i.fmt(f), Str(s) => s.fmt(f), + Bool(b) => b.fmt(f), Number(n) => n.fmt(f), Length(s) => s.fmt(f), - Bool(b) => b.fmt(f), Color(c) => c.fmt(f), Tuple(t) => t.fmt(f), NamedTuple(t) => t.fmt(f), @@ -89,22 +90,15 @@ impl Debug for Expr { } } -/// A unicode identifier. -/// -/// # Example -/// ```typst -/// [func: "hi", ident] -/// ^^^^ ^^^^^ -/// ``` +/// An identifier as defined by unicode with a few extra permissible characters. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct Ident(pub String); impl Ident { - /// Create a new identifier from a string checking that it is a valid - /// unicode identifier. - pub fn new(ident: S) -> Option where S: AsRef + Into { + /// Create a new identifier from a string checking that it is a valid. + pub fn new(ident: impl AsRef + Into) -> Option { if is_identifier(ident.as_ref()) { - Some(Ident(ident.into())) + Some(Self(ident.into())) } else { None } @@ -139,36 +133,36 @@ pub struct RgbaColor { pub b: u8, /// Alpha channel. pub a: u8, - /// Indicates whether this is a user-provided value or a - /// default value provided as a fail-over by the parser. - /// This color may be overwritten if this property is true. + /// This is true if this value was provided as a fail-over by the parser + /// because the user-defined value was invalid. This color may be + /// overwritten if this property is true. pub healed: bool, } impl RgbaColor { /// Constructs a new color. - pub fn new(r: u8, g: u8, b: u8, a: u8) -> RgbaColor { - RgbaColor { r, g, b, a, healed: false } + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a, healed: false } } /// Constructs a new color with the healed property set to true. - pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> RgbaColor { - RgbaColor { r, g, b, a, healed: true } + pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> Self { + Self { r, g, b, a, healed: true } } } impl FromStr for RgbaColor { type Err = ParseColorError; - /// Constructs a new color from a hex string like `7a03c2`. - /// Do not specify a leading `#`. - fn from_str(hex_str: &str) -> Result { + /// Constructs a new color from a hex string like `7a03c2`. Do not specify a + /// leading `#`. + fn from_str(hex_str: &str) -> Result { if !hex_str.is_ascii() { return Err(ParseColorError); } let len = hex_str.len(); - let long = len == 6 || len == 8; + let long = len == 6 || len == 8; let short = len == 3 || len == 4; let alpha = len == 4 || len == 8; @@ -192,7 +186,7 @@ impl FromStr for RgbaColor { } } - Ok(RgbaColor::new(values[0], values[1], values[2], values[3])) + Ok(Self::new(values[0], values[1], values[2], values[3])) } } @@ -200,14 +194,12 @@ impl Debug for RgbaColor { fn fmt(&self, f: &mut Formatter) -> fmt::Result { if f.alternate() { write!( - f, - "rgba({:02}, {:02}, {:02}, {:02})", + f, "rgba({:02}, {:02}, {:02}, {:02})", self.r, self.g, self.b, self.a, )?; } else { write!( - f, - "#{:02x}{:02x}{:02x}{:02x}", + f, "#{:02x}{:02x}{:02x}{:02x}", self.r, self.g, self.b, self.a, )?; } @@ -218,7 +210,7 @@ impl Debug for RgbaColor { } } -/// The error returned when parsing a [`RgbaColor`] from a string fails. +/// The error when parsing an `RgbaColor` fails. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct ParseColorError; @@ -226,7 +218,7 @@ impl std::error::Error for ParseColorError {} impl fmt::Display for ParseColorError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str("invalid color") + f.pad("invalid color") } } @@ -241,8 +233,8 @@ pub struct Tuple(pub SpanVec); impl Tuple { /// Create an empty tuple. - pub fn new() -> Tuple { - Tuple(vec![]) + pub fn new() -> Self { + Self(vec![]) } /// Add an element. @@ -278,13 +270,13 @@ impl Tuple { let mut i = 0; std::iter::from_fn(move || { while i < self.0.len() { - let val = V::parse(self.0[i].clone(), &mut Feedback::new()); - if val.is_some() { - self.0.remove(i); - return val; - } else { - i += 1; - } + let val = V::parse(self.0[i].clone(), &mut Feedback::new()); + if val.is_some() { + self.0.remove(i); + return val; + } else { + i += 1; + } } None }) @@ -305,16 +297,16 @@ impl Debug for Tuple { /// ``` #[derive(Debug, Clone, PartialEq)] pub struct NamedTuple { - /// The name of the tuple and where it is in the user source. + /// The name of the tuple. pub name: Spanned, /// The elements of the tuple. pub tuple: Spanned, } impl NamedTuple { - /// Create a named tuple from a tuple. - pub fn new(name: Spanned, tuple: Spanned) -> NamedTuple { - NamedTuple { name, tuple } + /// Create a named tuple from a name and a tuple. + pub fn new(name: Spanned, tuple: Spanned) -> Self { + Self { name, tuple } } } @@ -338,24 +330,14 @@ pub struct Object(pub SpanVec); /// A key-value pair in an object. #[derive(Debug, Clone, PartialEq)] pub struct Pair { - /// The key part. - /// ```typst - /// key: value - /// ^^^ - /// ``` pub key: Spanned, - /// The value part. - /// ```typst - /// key: value - /// ^^^^^ - /// ``` pub value: Spanned, } impl Object { /// Create an empty object. - pub fn new() -> Object { - Object(vec![]) + pub fn new() -> Self { + Self(vec![]) } /// Add a pair to object. @@ -384,13 +366,13 @@ impl Object { let mut i = 0; std::iter::from_fn(move || { while i < self.0.len() { - let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new()); - if let Some(val) = val { - let pair = self.0.remove(i); - return Some((pair.v.key, val)); - } else { - i += 1; - } + let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new()); + if let Some(val) = val { + let pair = self.0.remove(i); + return Some((pair.v.key, val)); + } else { + i += 1; + } } None }) diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 6b1f3d087..e1c9bbb07 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -4,19 +4,19 @@ #[macro_use] mod test; +pub mod decoration; +pub mod expr; +pub mod parsing; +pub mod scope; +pub mod span; +pub mod tokens; +pub mod tree; +pub mod value; + /// Basic types used around the syntax side. pub mod prelude { pub use super::expr::*; - pub use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; - pub use super::span::{SpanVec, Span, Spanned}; + pub use super::span::{Span, SpanVec, Spanned}; + pub use super::tree::{DynamicNode, SyntaxNode, SyntaxTree}; pub use super::value::*; } - -pub mod decoration; -pub mod expr; -pub mod tree; -pub mod parsing; -pub mod span; -pub mod scope; -pub mod tokens; -pub mod value; diff --git a/src/syntax/parsing.rs b/src/syntax/parsing.rs index bcbcb8d45..5141e455c 100644 --- a/src/syntax/parsing.rs +++ b/src/syntax/parsing.rs @@ -2,36 +2,34 @@ use std::str::FromStr; -use crate::{Pass, Feedback}; +use crate::{Feedback, Pass}; use super::decoration::Decoration; use super::expr::*; use super::scope::Scope; use super::span::{Pos, Span, Spanned}; -use super::tokens::{is_newline_char, Token, Tokens, TokenMode}; -use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; +use super::tokens::{is_newline_char, Token, TokenMode, Tokens}; +use super::tree::{DynamicNode, SyntaxNode, SyntaxTree}; -/// A function which parses a function call into a tree. +/// A function which parses a function call into a dynamic node. pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass>; /// Parse a function call. pub trait ParseCall { - /// 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). + /// Metadata whose value is passed to `parse`. 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. /// - /// 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. + /// For example, the functions `h` and `v` are built on the same type. type Meta: Clone; - /// Parse the header and body into this function given a context. + /// Parse the function call. fn parse( - header: FuncCall, + call: FuncCall, state: &ParseState, metadata: Self::Meta, - ) -> Pass where Self: Sized; + ) -> Pass + where + Self: Sized; } /// An invocation of a function. @@ -58,8 +56,8 @@ pub struct FuncArgs { impl FuncArgs { /// Create new empty function arguments. - pub fn new() -> FuncArgs { - FuncArgs { + pub fn new() -> Self { + Self { pos: Tuple::new(), key: Object::new(), } @@ -77,9 +75,7 @@ impl FuncArgs { /// Either a positional or keyword argument. #[derive(Debug, Clone, PartialEq)] pub enum FuncArg { - /// A positional argument. Pos(Expr), - /// A keyword argument. Key(Pair), } @@ -116,26 +112,21 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass { Token::Function { header, body, terminated } => { let parsed = FuncParser::new(header, body, state).parse(); feedback.extend_offset(parsed.feedback, span.start); - if !terminated { error!(@feedback, Span::at(span.end), "expected closing bracket"); } - parsed.output } Token::Star => SyntaxNode::ToggleBolder, Token::Underscore => SyntaxNode::ToggleItalic, Token::Backslash => SyntaxNode::Linebreak, - Token::Raw { raw, terminated } => { if !terminated { error!(@feedback, Span::at(span.end), "expected backtick"); } - SyntaxNode::Raw(unescape_raw(raw)) } - Token::Text(text) => SyntaxNode::Text(text.to_string()), Token::LineComment(_) | Token::BlockComment(_) => continue, @@ -153,17 +144,9 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass { struct FuncParser<'s> { state: &'s ParseState, - /// ```typst - /// [tokens][body] - /// ^^^^^^ - /// ``` + /// The tokens inside the header. tokens: Tokens<'s>, peeked: Option>>>, - /// The spanned body string if there is a body. - /// ```typst - /// [tokens][body] - /// ^^^^ - /// ``` body: Option>, feedback: Feedback, } @@ -173,8 +156,8 @@ impl<'s> FuncParser<'s> { header: &'s str, body: Option>, state: &'s ParseState, - ) -> FuncParser<'s> { - FuncParser { + ) -> Self { + Self { state, // Start at column 1 because the opening bracket is also part of // the function, but not part of the `header` string. @@ -190,7 +173,7 @@ impl<'s> FuncParser<'s> { let name = header.name.v.as_str(); let (parser, deco) = match self.state.scope.get_parser(name) { // The function exists in the scope. - Some(parser) => (parser, Decoration::ValidFuncName), + Some(parser) => (parser, Decoration::ResolvedFunc), // The function does not exist in the scope. The parser that is // returned here is a fallback parser which exists to make sure @@ -199,7 +182,7 @@ impl<'s> FuncParser<'s> { None => { error!(@self.feedback, header.name.span, "unknown function"); let parser = self.state.scope.get_fallback_parser(); - (parser, Decoration::InvalidFuncName) + (parser, Decoration::UnresolvedFunc) } }; @@ -261,9 +244,8 @@ impl<'s> FuncParser<'s> { self.skip_white(); let key = ident; - self.feedback.decorations.push( - Spanned::new(Decoration::ArgumentKey, key.span) - ); + self.feedback.decorations + .push(Spanned::new(Decoration::ArgumentKey, key.span)); let value = try_opt_or!(self.parse_expr(), { self.expected("value"); @@ -399,9 +381,9 @@ impl FuncParser<'_> { self.eat_span(Expr::Str(unescape_string(string))) } + Token::ExprBool(b) => self.eat_span(Expr::Bool(b)), Token::ExprNumber(n) => self.eat_span(Expr::Number(n)), Token::ExprLength(s) => self.eat_span(Expr::Length(s)), - Token::ExprBool(b) => self.eat_span(Expr::Bool(b)), Token::ExprHex(s) => { if let Ok(color) = RgbaColor::from_str(s) { self.eat_span(Expr::Color(color)) @@ -411,7 +393,7 @@ impl FuncParser<'_> { let healed = RgbaColor::new_healed(0, 0, 0, 255); self.eat_span(Expr::Color(healed)) } - }, + } // This could be a tuple or a parenthesized expression. We parse as // a tuple in any case and coerce the tuple into a value if it is @@ -500,9 +482,8 @@ impl FuncParser<'_> { continue; } - self.feedback.decorations.push( - Spanned::new(Decoration::ObjectKey, key.span) - ); + self.feedback.decorations + .push(Spanned::new(Decoration::ObjectKey, key.span)); self.skip_white(); let value = try_opt_or!(self.parse_expr(), { @@ -622,7 +603,8 @@ impl<'s> FuncParser<'s> { } fn pos(&self) -> Pos { - self.peeked.flatten() + self.peeked + .flatten() .map(|s| s.span.start) .unwrap_or_else(|| self.tokens.pos()) } @@ -687,10 +669,10 @@ mod tests { use super::*; use Decoration::*; - use Expr::{Number as Num, Length as Len, Bool}; + use Expr::{Bool, Length as Len, Number as Num}; use SyntaxNode::{ - Space as S, ToggleItalic as Italic, ToggleBolder as Bold, - Parbreak, Linebreak, + Space as S, Parbreak, Linebreak, ToggleItalic as Italic, + ToggleBolder as Bold, }; /// Test whether the given string parses into @@ -882,7 +864,7 @@ mod tests { p!("🌎\n*/[n]" => [(0:0, 0:1, T("🌎")), (0:1, 1:0, S), (1:2, 1:5, func!((0:1, 0:2, "n")))], [(1:0, 1:2, "unexpected end of block comment")], - [(1:3, 1:4, ValidFuncName)], + [(1:3, 1:4, ResolvedFunc)], ); } @@ -905,12 +887,12 @@ mod tests { p!("[hi]" => [func!("hi")], [(0:1, 0:3, "unknown function")], - [(0:1, 0:3, InvalidFuncName)], + [(0:1, 0:3, UnresolvedFunc)], ); // A valid name. - p!("[f]" => [func!("f")], [], [(0:1, 0:2, ValidFuncName)]); - p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ValidFuncName)]); + p!("[f]" => [func!("f")], [], [(0:1, 0:2, ResolvedFunc)]); + p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ResolvedFunc)]); // An invalid token for a name. p!("[12]" => [func!("")], [(0:1, 0:3, "expected function name, found number")], []); @@ -923,7 +905,7 @@ mod tests { // Valid. p!("[val: true]" => [func!["val": (Bool(true))]], [], - [(0:1, 0:4, ValidFuncName)], + [(0:1, 0:4, ResolvedFunc)], ); // No colon before arg. @@ -936,7 +918,7 @@ mod tests { p!("[val/🌎:$]" => [func!("val")], [(0:4, 0:4, "expected colon")], - [(0:1, 0:4, ValidFuncName)], + [(0:1, 0:4, ResolvedFunc)], ); // String in invalid header without colon still parsed as string @@ -1159,20 +1141,20 @@ mod tests { // Correct p!("[val: x=true]" => [func!("val": (), { "x" => Bool(true) })], [], - [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)], + [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ResolvedFunc)], ); // Spacing around keyword arguments p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" => [S, func!("val": (), { "hi" => Str("s\n") })], [], - [(2:1, 2:3, ArgumentKey), (1:2, 1:5, ValidFuncName)], + [(2:1, 2:3, ArgumentKey), (1:2, 1:5, ResolvedFunc)], ); // Missing value p!("[val: x=]" => [func!("val")], [(0:8, 0:8, "expected value")], - [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ValidFuncName)], + [(0:6, 0:7, ArgumentKey), (0:1, 0:4, ResolvedFunc)], ); } @@ -1180,7 +1162,7 @@ mod tests { fn parse_multiple_mixed_arguments() { p!("[val: 12pt, key=value]" => [func!("val": (Len(Length::pt(12.0))), { "key" => Id("value") })], [], - [(0:12, 0:15, ArgumentKey), (0:1, 0:4, ValidFuncName)], + [(0:12, 0:15, ArgumentKey), (0:1, 0:4, ResolvedFunc)], ); pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") }); } @@ -1197,7 +1179,7 @@ mod tests { p!("[val: [hi]]" => [func!("val")], [(0:6, 0:10, "expected argument, found function")], - [(0:1, 0:4, ValidFuncName)], + [(0:1, 0:4, ResolvedFunc)], ); } @@ -1208,7 +1190,7 @@ mod tests { [func!("val": (Bool(true), Id("you")), {})], [(0:10, 0:10, "expected comma"), (0:10, 0:11, "expected argument, found equals sign")], - [(0:1, 0:4, ValidFuncName)], + [(0:1, 0:4, ResolvedFunc)], ); // Unexpected equals. @@ -1223,14 +1205,14 @@ mod tests { [func!("val": (Id("key"), Num(12.0)), {})], [(0:9, 0:9, "expected comma"), (0:9, 0:10, "expected argument, found colon")], - [(0:1, 0:4, ValidFuncName)], + [(0:1, 0:4, ResolvedFunc)], ); // Invalid colon after unkeyable positional argument. p!("[val: true:12]" => [func!("val": (Bool(true), Num(12.0)), {})], [(0:10, 0:10, "expected comma"), (0:10, 0:11, "expected argument, found colon")], - [(0:1, 0:4, ValidFuncName)], + [(0:1, 0:4, ResolvedFunc)], ); } @@ -1274,13 +1256,13 @@ mod tests { // Space before function p!(" [val]" => [(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "val")))], [], - [(0:2, 0:5, ValidFuncName)], + [(0:2, 0:5, ResolvedFunc)], ); // Newline before function p!(" \n\r\n[val]" => [(0:0, 2:0, Parbreak), (2:0, 2:5, func!((0:1, 0:4, "val")))], [], - [(2:1, 2:4, ValidFuncName)], + [(2:1, 2:4, ResolvedFunc)], ); // Content before function @@ -1293,7 +1275,7 @@ mod tests { (0:19, 0:20, T("🌎")) ], [], - [(0:7, 0:10, ValidFuncName)], + [(0:7, 0:10, ResolvedFunc)], ); // Nested function @@ -1308,7 +1290,7 @@ mod tests { ])) ], [], - [(0:2, 0:5, ValidFuncName), (1:6, 1:9, ValidFuncName)], + [(0:2, 0:5, ResolvedFunc), (1:6, 1:9, ResolvedFunc)], ); } } diff --git a/src/syntax/scope.rs b/src/syntax/scope.rs index 8fdad6a01..aac2b1b80 100644 --- a/src/syntax/scope.rs +++ b/src/syntax/scope.rs @@ -1,4 +1,4 @@ -//! Scopes containing function parsers. +//! Mapping of function names to function parsers. use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; @@ -15,33 +15,31 @@ pub struct Scope { impl Scope { /// Create a new empty scope with a fallback parser that is invoked when no /// match is found. - pub fn new() -> Scope - where F: ParseCall + DynamicNode + 'static { - Scope { + pub fn new() -> Self + where + F: ParseCall + DynamicNode + 'static + { + Self { parsers: HashMap::new(), fallback: make_parser::(()), } } - /// Create a new scope with the standard functions contained. - pub fn with_std() -> Scope { - crate::library::std() - } - - /// Associate the given name with a type that is parseable into a function. + /// Associate the given function name with a dynamic node type. pub fn add(&mut self, name: &str) - where F: ParseCall + DynamicNode + 'static { + where + F: ParseCall + DynamicNode + 'static + { self.add_with_meta::(name, ()); } - /// Add a parseable type with additional metadata that is given to the - /// parser (other than the default of `()`). + /// Add a dynamic node type with additional metadata that is passed to the + /// parser. pub fn add_with_meta(&mut self, name: &str, metadata: ::Meta) - where F: ParseCall + DynamicNode + 'static { - self.parsers.insert( - name.to_string(), - make_parser::(metadata), - ); + where + F: ParseCall + DynamicNode + 'static + { + self.parsers.insert(name.to_string(), make_parser::(metadata)); } /// Return the parser with the given name if there is one. @@ -57,14 +55,14 @@ impl Scope { impl Debug for Scope { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.debug_set() - .entries(self.parsers.keys()) - .finish() + f.debug_set().entries(self.parsers.keys()).finish() } } fn make_parser(metadata: ::Meta) -> Box -where F: ParseCall + DynamicNode + 'static { +where + F: ParseCall + DynamicNode + 'static, +{ Box::new(move |f, s| { F::parse(f, s, metadata.clone()) .map(|tree| Box::new(tree) as Box) diff --git a/src/syntax/span.rs b/src/syntax/span.rs index 9b3c7d24f..af293718f 100644 --- a/src/syntax/span.rs +++ b/src/syntax/span.rs @@ -36,13 +36,13 @@ pub struct Spanned { impl Spanned { /// Create a new instance from a value and its span. - pub fn new(v: T, span: Span) -> Spanned { - Spanned { v, span } + pub fn new(v: T, span: Span) -> Self { + Self { v, span } } /// Create a new instance from a value with the zero span. - pub fn zero(v: T) -> Spanned { - Spanned { v, span: Span::ZERO } + pub fn zero(v: T) -> Self { + Self { v, span: Span::ZERO } } /// Access the value. @@ -51,12 +51,12 @@ impl Spanned { } /// Map the value using a function while keeping the span. - pub fn map(self, f: F) -> Spanned where F: FnOnce(T) -> V { + pub fn map(self, f: impl FnOnce(T) -> U) -> Spanned { Spanned { v: f(self.v), span: self.span } } /// Maps the span while keeping the value. - pub fn map_span(mut self, f: F) -> Spanned where F: FnOnce(Span) -> Span { + pub fn map_span(mut self, f: impl FnOnce(Span) -> Span) -> Self { self.span = f(self.span); self } @@ -91,35 +91,35 @@ pub struct Span { impl Span { /// The zero span. - pub const ZERO: Span = Span { start: Pos::ZERO, end: Pos::ZERO }; + pub const ZERO: Self = Self { start: Pos::ZERO, end: Pos::ZERO }; /// Create a new span from start and end positions. - pub fn new(start: Pos, end: Pos) -> Span { - Span { start, end } + pub fn new(start: Pos, end: Pos) -> Self { + Self { start, end } } /// Create a span including just a single position. - pub fn at(pos: Pos) -> Span { - Span { start: pos, end: pos } + pub fn at(pos: Pos) -> Self { + Self { start: pos, end: pos } } /// Create a new span with the earlier start and later end position. - pub fn merge(a: Span, b: Span) -> Span { - Span { + pub fn merge(a: Self, b: Self) -> Self { + Self { start: a.start.min(b.start), end: a.end.max(b.end), } } /// Expand a span by merging it with another span. - pub fn expand(&mut self, other: Span) { - *self = Span::merge(*self, other) + pub fn expand(&mut self, other: Self) { + *self = Self::merge(*self, other) } } impl Offset for Span { fn offset(self, by: Pos) -> Self { - Span { + Self { start: self.start.offset(by), end: self.end.offset(by), } @@ -144,31 +144,31 @@ pub struct Pos { impl Pos { /// The line 0, column 0 position. - pub const ZERO: Pos = Pos { line: 0, column: 0 }; + pub const ZERO: Self = Self { line: 0, column: 0 }; /// Create a new position from line and column. - pub fn new(line: usize, column: usize) -> Pos { - Pos { line, column } + pub fn new(line: usize, column: usize) -> Self { + Self { line, column } } } impl Offset for Pos { - fn offset(self, by: Pos) -> Self { + fn offset(self, by: Self) -> Self { by + self } } impl Add for Pos { - type Output = Pos; + type Output = Self; - fn add(self, rhs: Pos) -> Pos { + fn add(self, rhs: Self) -> Self { if rhs.line == 0 { - Pos { + Self { line: self.line, - column: self.column + rhs.column + column: self.column + rhs.column, } } else { - Pos { + Self { line: self.line + rhs.line, column: rhs.column, } @@ -177,16 +177,16 @@ impl Add for Pos { } impl Sub for Pos { - type Output = Pos; + type Output = Self; - fn sub(self, rhs: Pos) -> Pos { + fn sub(self, rhs: Self) -> Self { if self.line == rhs.line { - Pos { + Self { line: 0, - column: self.column - rhs.column + column: self.column - rhs.column, } } else { - Pos { + Self { line: self.line - rhs.line, column: self.column, } diff --git a/src/syntax/test.rs b/src/syntax/test.rs index 504bc3340..db7a2de2e 100644 --- a/src/syntax/test.rs +++ b/src/syntax/test.rs @@ -2,15 +2,16 @@ use std::fmt::Debug; use crate::func::parse_maybe_body; use super::decoration::Decoration; -use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair}; -use super::parsing::{FuncHeader, FuncArgs, FuncArg}; +use super::expr::{Expr, Ident, NamedTuple, Object, Pair, Tuple}; +use super::parsing::{FuncArg, FuncArgs, FuncHeader}; use super::span::Spanned; use super::tokens::Token; -use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; +use super::tree::{DynamicNode, SyntaxNode, SyntaxTree}; -/// Check whether the expected and found results are the same. pub fn check(src: &str, exp: T, found: T, cmp_spans: bool) -where T: Debug + PartialEq + SpanlessEq { +where + T: Debug + PartialEq + SpanlessEq, +{ let cmp = if cmp_spans { PartialEq::eq } else { SpanlessEq::spanless_eq }; if !cmp(&exp, &found) { println!("source: {:?}", src); @@ -41,7 +42,7 @@ macro_rules! span_vec { } macro_rules! span_item { - (($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => ({ + (($sl:tt:$sc:tt, $el:tt:$ec:tt, $v:expr)) => {{ use $crate::syntax::span::{Pos, Span, Spanned}; Spanned { span: Span::new( @@ -50,7 +51,7 @@ macro_rules! span_item { ), v: $v } - }); + }}; ($v:expr) => { $crate::syntax::span::Spanned::zero($v) @@ -70,7 +71,7 @@ function! { let cloned = header.clone(); header.args.pos.0.clear(); header.args.key.0.clear(); - DebugFn { + Self { header: cloned, body: parse_maybe_body(body, state, f), } @@ -80,7 +81,7 @@ function! { } /// Compares elements by only looking at values and ignoring spans. -pub trait SpanlessEq { +pub trait SpanlessEq { fn spanless_eq(&self, other: &Rhs) -> bool; } @@ -102,20 +103,21 @@ impl SpanlessEq for SyntaxNode { impl SpanlessEq for DebugFn { fn spanless_eq(&self, other: &DebugFn) -> bool { self.header.spanless_eq(&other.header) - && self.body.spanless_eq(&other.body) + && self.body.spanless_eq(&other.body) } } impl SpanlessEq for FuncHeader { fn spanless_eq(&self, other: &Self) -> bool { - self.name.spanless_eq(&other.name) && self.args.spanless_eq(&other.args) + self.name.spanless_eq(&other.name) + && self.args.spanless_eq(&other.args) } } impl SpanlessEq for FuncArgs { fn spanless_eq(&self, other: &Self) -> bool { self.key.spanless_eq(&other.key) - && self.pos.spanless_eq(&other.pos) + && self.pos.spanless_eq(&other.pos) } } @@ -154,7 +156,7 @@ impl SpanlessEq for Tuple { impl SpanlessEq for NamedTuple { fn spanless_eq(&self, other: &NamedTuple) -> bool { self.name.v == other.name.v - && self.tuple.v.spanless_eq(&other.tuple.v) + && self.tuple.v.spanless_eq(&other.tuple.v) } } @@ -173,7 +175,7 @@ impl SpanlessEq for Pair { impl SpanlessEq for Vec { fn spanless_eq(&self, other: &Vec) -> bool { self.len() == other.len() - && self.iter().zip(other).all(|(x, y)| x.spanless_eq(&y)) + && self.iter().zip(other).all(|(x, y)| x.spanless_eq(&y)) } } diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs index 1ea114494..ef2494714 100644 --- a/src/syntax/tokens.rs +++ b/src/syntax/tokens.rs @@ -9,7 +9,6 @@ use super::span::{Pos, Span, Spanned}; use Token::*; use TokenMode::*; - /// A minimal semantic entity of source code. #[derive(Debug, Copy, Clone, PartialEq)] pub enum Token<'s> { @@ -71,14 +70,14 @@ pub enum Token<'s> { /// a String. The escaping is done later in the parser. string: &'s str, /// Whether the closing quote was present. - terminated: bool + terminated: bool, }, + /// A boolean in a function header: `true | false`. + ExprBool(bool), /// A number in a function header: `3.14`. ExprNumber(f64), /// A length in a function header: `12pt`. ExprLength(Length), - /// A boolean in a function header: `true | false`. - ExprBool(bool), /// A hex value in a function header: `#20d82a`. ExprHex(&'s str), /// A plus in a function header, signifying the addition of expressions. @@ -130,9 +129,9 @@ impl<'s> Token<'s> { Equals => "equals sign", ExprIdent(_) => "identifier", ExprStr { .. } => "string", + ExprBool(_) => "bool", ExprNumber(_) => "number", ExprLength(_) => "length", - ExprBool(_) => "bool", ExprHex(_) => "hex value", Plus => "plus", Hyphen => "minus", @@ -173,8 +172,8 @@ impl<'s> Tokens<'s> { /// /// The first token's span starts an the given `offset` position instead of /// the zero position. - pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Tokens<'s> { - Tokens { + pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Self { + Self { src, mode, iter: src.chars().peekable(), @@ -200,7 +199,7 @@ impl<'s> Iterator for Tokens<'s> { type Item = Spanned>; /// Parse the next token in the source code. - fn next(&mut self) -> Option>> { + fn next(&mut self) -> Option { let start = self.pos(); let first = self.eat()?; @@ -366,7 +365,7 @@ impl<'s> Tokens<'s> { } let end = self.index(); - (&self.src[start .. end], terminated) + (&self.src[start..end], terminated) } fn read_string(&mut self) -> Token<'s> { @@ -404,7 +403,7 @@ impl<'s> Tokens<'s> { Some(c) if is_escapable(c) => { let index = self.index(); self.eat(); - Text(&self.src[index .. index + c.len_utf8()]) + Text(&self.src[index..index + c.len_utf8()]) } Some(c) if c.is_whitespace() => Backslash, Some(_) => Text("\\"), @@ -442,13 +441,13 @@ impl<'s> Tokens<'s> { /// Returns the string from the index where this was called offset by /// `offset_start` to the end offset by `offset_end`. The end is before or /// after the match depending on `eat_match`. - fn read_string_until( + fn read_string_until( &mut self, - mut f: F, + mut f: impl FnMut(char) -> bool, eat_match: bool, offset_start: isize, offset_end: isize, - ) -> (&'s str, bool) where F: FnMut(char) -> bool { + ) -> (&'s str, bool) { let start = ((self.index() as isize) + offset_start) as usize; let mut matched = false; @@ -469,7 +468,7 @@ impl<'s> Tokens<'s> { end = ((end as isize) + offset_end) as usize; } - (&self.src[start .. end], matched) + (&self.src[start..end], matched) } fn eat(&mut self) -> Option { @@ -493,7 +492,7 @@ impl<'s> Tokens<'s> { fn parse_percentage(text: &str) -> Option { if text.ends_with('%') { - text[.. text.len() - 1].parse::().ok() + text[..text.len() - 1].parse::().ok() } else { None } @@ -503,7 +502,7 @@ fn parse_percentage(text: &str) -> Option { pub fn is_newline_char(character: char) -> bool { match character { // Line Feed, Vertical Tab, Form Feed, Carriage Return. - '\x0A' ..= '\x0D' => true, + '\x0A'..='\x0D' => true, // Next Line, Line Separator, Paragraph Separator. '\u{0085}' | '\u{2028}' | '\u{2029}' => true, _ => false, @@ -544,15 +543,15 @@ mod tests { LeftParen as LP, RightParen as RP, LeftBrace as LB, RightBrace as RB, ExprIdent as Id, + ExprBool as Bool, ExprNumber as Num, ExprLength as Len, - ExprBool as Bool, ExprHex as Hex, - Text as T, Plus, Hyphen as Min, - Star, Slash, + Star, + Text as T, }; /// Test whether the given string tokenizes into the given list of tokens. diff --git a/src/syntax/tree.rs b/src/syntax/tree.rs index 41a03faeb..28997e7cb 100644 --- a/src/syntax/tree.rs +++ b/src/syntax/tree.rs @@ -6,7 +6,7 @@ use std::fmt::Debug; use crate::layout::Layout; use super::span::SpanVec; -/// A list of nodes which forms a tree together with the nodes' children. +/// A collection of nodes which form a tree together with the nodes' children. pub type SyntaxTree = SpanVec; /// A syntax node, which encompasses a single logical entity of parsed source @@ -27,7 +27,7 @@ pub enum SyntaxNode { ToggleItalic, /// Bolder was enabled / disabled. ToggleBolder, - /// A subtree, typically a function invocation. + /// A dynamic node, create through function invocations in source code. Dyn(Box), } @@ -65,13 +65,16 @@ pub trait DynamicNode: Debug + Layout { impl dyn DynamicNode { /// Downcast this dynamic node to a concrete node. - pub fn downcast(&self) -> Option<&N> where N: DynamicNode + 'static { - self.as_any().downcast_ref::() + pub fn downcast(&self) -> Option<&T> + where + T: DynamicNode + 'static, + { + self.as_any().downcast_ref::() } } impl PartialEq for dyn DynamicNode { - fn eq(&self, other: &dyn DynamicNode) -> bool { + fn eq(&self, other: &Self) -> bool { self.dyn_eq(other) } } @@ -82,7 +85,10 @@ impl Clone for Box { } } -impl DynamicNode for T where T: Debug + PartialEq + Clone + Layout + 'static { +impl DynamicNode for T +where + T: Debug + PartialEq + Clone + Layout + 'static, +{ fn as_any(&self) -> &dyn Any { self } diff --git a/src/syntax/value.rs b/src/syntax/value.rs index 5c264ca04..b7211eaf0 100644 --- a/src/syntax/value.rs +++ b/src/syntax/value.rs @@ -2,18 +2,20 @@ use fontdock::{FontStyle, FontWeight, FontWidth}; -use crate::Feedback; use crate::layout::prelude::*; use crate::length::{Length, ScaleLength}; use crate::paper::Paper; -use super::span::Spanned; +use crate::Feedback; use super::expr::*; +use super::span::Spanned; /// Value types are used to extract values from functions, tuples and /// objects. They represent the value part of an argument. +/// +/// # Example /// ```typst -/// [func: value, key=value] -/// ^^^^^ ^^^^^ +/// [func: 12pt, key="these are both values"] +/// ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ /// ``` pub trait Value: Sized { /// Try to parse this value from an expression. @@ -53,8 +55,8 @@ macro_rules! match_value { match_value!(Expr, "expression", e => e); match_value!(Ident, "identifier", Expr::Ident(i) => i); match_value!(String, "string", Expr::Str(s) => s); -match_value!(f64, "number", Expr::Number(n) => n); match_value!(bool, "bool", Expr::Bool(b) => b); +match_value!(f64, "number", Expr::Number(n) => n); match_value!(Length, "length", Expr::Length(l) => l); match_value!(Tuple, "tuple", Expr::Tuple(t) => t); match_value!(Object, "object", Expr::Object(o) => o); @@ -63,7 +65,7 @@ match_value!(ScaleLength, "number or length", Expr::Number(scale) => ScaleLength::Scaled(scale), ); -/// A value type that matches [`Expr::Ident`] and [`Expr::Str`] and implements +/// A value type that matches identifiers and strings and implements /// `Into`. pub struct StringLike(pub String); @@ -101,7 +103,7 @@ macro_rules! ident_value { } ident_value!(Dir, "direction", |s| match s { - "ltr" => Some(LTT), + "ltr" => Some(LTR), "rtl" => Some(RTL), "ttb" => Some(TTB), "btt" => Some(BTT), @@ -109,11 +111,11 @@ ident_value!(Dir, "direction", |s| match s { }); ident_value!(SpecAlign, "alignment", |s| match s { - "left" => Some(SpecAlign::Left), - "right" => Some(SpecAlign::Right), - "top" => Some(SpecAlign::Top), - "bottom" => Some(SpecAlign::Bottom), - "center" => Some(SpecAlign::Center), + "left" => Some(Self::Left), + "right" => Some(Self::Right), + "top" => Some(Self::Top), + "bottom" => Some(Self::Bottom), + "center" => Some(Self::Center), _ => None, }); @@ -127,7 +129,7 @@ impl Value for FontWeight { const MIN: u16 = 100; const MAX: u16 = 900; - Some(FontWeight(if weight < MIN as f64 { + Some(Self(if weight < MIN as f64 { error!(@f, expr.span, "the minimum font weight is {}", MIN); MIN } else if weight > MAX as f64 { @@ -163,7 +165,7 @@ impl Value for FontWidth { const MIN: u16 = 1; const MAX: u16 = 9; - FontWidth::new(if width < MIN as f64 { + Self::new(if width < MIN as f64 { error!(@f, expr.span, "the minimum font width is {}", MIN); MIN } else if width > MAX as f64 { diff --git a/tests/test_typeset.rs b/tests/test_typeset.rs index 6ac34c85c..880818daa 100644 --- a/tests/test_typeset.rs +++ b/tests/test_typeset.rs @@ -5,21 +5,21 @@ use std::fs::{self, File}; use std::io::BufWriter; use std::rc::Rc; +use fontdock::fs::{FsIndex, FsProvider}; +use fontdock::FontLoader; use futures_executor::block_on; -use raqote::{DrawTarget, Source, SolidSource, PathBuilder, Vector, Transform}; +use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector}; use ttf_parser::OutlineBuilder; -use typstc::Typesetter; +use typstc::export::pdf; use typstc::font::{DynProvider, SharedFontLoader}; use typstc::geom::{Size, Value4}; -use typstc::layout::MultiLayout; use typstc::layout::elements::{LayoutElement, Shaped}; +use typstc::layout::MultiLayout; use typstc::length::Length; -use typstc::style::PageStyle; use typstc::paper::PaperClass; -use typstc::export::pdf; -use fontdock::FontLoader; -use fontdock::fs::{FsIndex, FsProvider}; +use typstc::style::PageStyle; +use typstc::Typesetter; const TEST_DIR: &str = "tests"; const OUT_DIR: &str = "tests/out"; @@ -38,12 +38,7 @@ fn main() { continue; } - let name = path - .file_stem() - .unwrap() - .to_string_lossy() - .to_string(); - + let name = path.file_stem().unwrap().to_string_lossy().to_string(); if filter.matches(&name) { let src = fs::read_to_string(&path).unwrap(); filtered.push((name, src)); @@ -93,18 +88,15 @@ fn test( let typeset = block_on(typesetter.typeset(src)); let layouts = typeset.output; for diagnostic in typeset.feedback.diagnostics { - println!(" {:?} {:?}: {}", - diagnostic.v.level, - diagnostic.span, - diagnostic.v.message + println!( + " {:?} {:?}: {}", + diagnostic.v.level, diagnostic.span, diagnostic.v.message, ); } - // Render the PNG file. let png_path = format!("{}/{}.png", OUT_DIR, name); render(&layouts, &loader, 3.0).write_png(png_path).unwrap(); - // Write the PDF file. let pdf_path = format!("{}/{}.pdf", OUT_DIR, name); let file = BufWriter::new(File::create(pdf_path).unwrap()); pdf::export(&layouts, &loader, file).unwrap(); @@ -116,19 +108,19 @@ struct TestFilter { } impl TestFilter { - fn new(args: impl Iterator) -> TestFilter { + fn new(args: impl Iterator) -> Self { let mut filter = Vec::new(); let mut perfect = false; for arg in args { match arg.as_str() { - "--nocapture" => {}, + "--nocapture" => {} "=" => perfect = true, _ => filter.push(arg), } } - TestFilter { filter, perfect } + Self { filter, perfect } } fn matches(&self, name: &str) -> bool { @@ -136,7 +128,7 @@ impl TestFilter { self.filter.iter().any(|p| name == p) } else { self.filter.is_empty() - || self.filter.iter().any(|p| name.contains(p)) + || self.filter.iter().any(|p| name.contains(p)) } } } @@ -174,15 +166,13 @@ fn render( for &(pos, ref element) in &layout.elements.0 { match element { - LayoutElement::Text(shaped) => { - render_shaped( - &mut surface, - loader, - shaped, - scale * pos + offset, - scale, - ); - }, + LayoutElement::Text(shaped) => render_shaped( + &mut surface, + loader, + shaped, + scale * pos + offset, + scale, + ), } } @@ -215,7 +205,7 @@ fn render_shaped( let t = Transform::create_scale(s as f32, -s as f32) .post_translate(Vector::new(x as f32, y as f32)); - surface.fill( + surface.fill( &path.transform(&t), &Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }), &Default::default(),