Formatting, documentation and small improvements 🧽

This commit is contained in:
Laurenz 2020-08-03 16:01:23 +02:00
parent 5a8f2fb73d
commit dbfb3d2ced
35 changed files with 837 additions and 954 deletions

View File

@ -25,7 +25,7 @@ fs = ["fontdock/fs"]
[dev-dependencies] [dev-dependencies]
futures-executor = "0.3" futures-executor = "0.3"
serde_json = "1" serde_json = "1"
raqote = { version = "0.7", default-features = false } raqote = { version = "0.8", default-features = false }
[[test]] [[test]]
name = "test-typeset" name = "test-typeset"

View File

@ -1,44 +1,37 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::error::Error; use std::fs::{read_to_string, File};
use std::fs::{File, read_to_string};
use std::io::BufWriter; use std::io::BufWriter;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use futures_executor::block_on;
use fontdock::fs::{FsIndex, FsProvider}; use fontdock::fs::{FsIndex, FsProvider};
use fontdock::FontLoader; use fontdock::FontLoader;
use typstc::Typesetter; use futures_executor::block_on;
use typstc::font::DynProvider;
use typstc::export::pdf; use typstc::export::pdf;
use typstc::font::DynProvider;
use typstc::Typesetter;
fn main() { fn main() {
if let Err(err) = run() { let args: Vec<_> = std::env::args().collect();
eprintln!("error: {}", err);
std::process::exit(1);
}
}
fn run() -> Result<(), Box<dyn Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 || args.len() > 3 { if args.len() < 2 || args.len() > 3 {
println!("Usage: typst src.typ [out.pdf]"); println!("Usage: typst src.typ [out.pdf]");
std::process::exit(0); return;
} }
let source = Path::new(&args[1]); let src_path = Path::new(&args[1]);
let dest = if args.len() <= 2 { let dest_path = if args.len() <= 2 {
source.with_extension("pdf") src_path.with_extension("pdf")
} else { } else {
PathBuf::from(&args[2]) PathBuf::from(&args[2])
}; };
if source == dest { if src_path == dest_path {
Err("source and destination path are the same")?; panic!("source and destination path are the same");
} }
let src = read_to_string(source) let src = read_to_string(src_path)
.map_err(|_| "failed to read from source file")?; .expect("failed to read from source file");
let mut index = FsIndex::new(); let mut index = FsIndex::new();
index.search_dir("fonts"); index.search_dir("fonts");
@ -53,8 +46,10 @@ fn run() -> Result<(), Box<dyn Error>> {
let typesetter = Typesetter::new(loader.clone()); let typesetter = Typesetter::new(loader.clone());
let layouts = block_on(typesetter.typeset(&src)).output; let layouts = block_on(typesetter.typeset(&src)).output;
let writer = BufWriter::new(File::create(&dest)?); let file = File::create(&dest_path)
pdf::export(&layouts, &loader, writer)?; .expect("failed to create output file");
Ok(()) let writer = BufWriter::new(file);
pdf::export(&layouts, &loader, writer)
.expect("failed to export pdf");
} }

View File

@ -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 //! 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 //! layout on a best effort process, generating diagnostics for incorrect
//! generate errors and warnings. //! things.
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use serde::Serialize; use serde::Serialize;

View File

@ -1,28 +1,31 @@
//! Exporting of layouts into _PDF_ documents. //! Exporting into _PDF_ documents.
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{self, Write}; use std::io::{self, Write};
use tide::{PdfWriter, Rect, Ref, Trailer, Version}; use fontdock::FaceId;
use tide::content::Content; use tide::content::Content;
use tide::doc::{Catalog, Page, PageTree, Resource, Text}; use tide::doc::{Catalog, Page, PageTree, Resource, Text};
use tide::font::{ use tide::font::{
CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags, Type0Font, CIDFont, CIDFontType, CIDSystemInfo, CMap, CMapEncoding, FontDescriptor,
CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord, FontFlags, FontStream, GlyphUnit, Type0Font, WidthRecord,
}; };
use tide::{PdfWriter, Rect, Ref, Trailer, Version};
use fontdock::FaceId;
use ttf_parser::{name_id, GlyphId}; use ttf_parser::{name_id, GlyphId};
use crate::SharedFontLoader;
use crate::layout::{MultiLayout, BoxLayout};
use crate::layout::elements::LayoutElement; use crate::layout::elements::LayoutElement;
use crate::layout::{BoxLayout, MultiLayout};
use crate::length::Length; use crate::length::Length;
use crate::SharedFontLoader;
/// Export a layouted list of boxes. The same font loader as used for /// Export a list of layouts into a _PDF_ document.
/// layouting needs to be passed in here since the layout only contains ///
/// indices referencing the loaded faces. The raw PDF ist written into the /// This creates one page per layout. Additionally to the layouts, you need to
/// target writable, returning the number of bytes written. /// 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<W: Write>( pub fn export<W: Write>(
layout: &MultiLayout, layout: &MultiLayout,
loader: &SharedFontLoader, loader: &SharedFontLoader,
@ -31,22 +34,20 @@ pub fn export<W: Write>(
PdfExporter::new(layout, loader, target)?.write() PdfExporter::new(layout, loader, target)?.write()
} }
/// The data relevant to the export of one document.
struct PdfExporter<'a, W: Write> { struct PdfExporter<'a, W: Write> {
writer: PdfWriter<W>, writer: PdfWriter<W>,
layouts: &'a MultiLayout, layouts: &'a MultiLayout,
loader: &'a SharedFontLoader, loader: &'a SharedFontLoader,
/// Since we cross-reference pages and faces with their IDs already in the /// We need to know exactly which indirect reference id will be used for
/// document catalog, we need to know exactly which ID is used for what from /// which objects up-front to correctly declare the document catalogue, page
/// the beginning. Thus, we compute a range for each category of object and /// tree and so on. These offsets are computed in the beginning and stored
/// stored these here. /// here.
offsets: Offsets, offsets: Offsets,
// Font remapping, see below at `remap_fonts`. // Font remapping, see below at `remap_fonts`.
to_pdf: HashMap<FaceId, usize>, to_pdf: HashMap<FaceId, usize>,
to_fontdock: Vec<FaceId>, to_layout: Vec<FaceId>,
} }
/// Indicates which range of PDF IDs will be used for which contents.
struct Offsets { struct Offsets {
catalog: Ref, catalog: Ref,
page_tree: Ref, page_tree: Ref,
@ -58,27 +59,24 @@ struct Offsets {
const NUM_OBJECTS_PER_FONT: u32 = 5; const NUM_OBJECTS_PER_FONT: u32 = 5;
impl<'a, W: Write> PdfExporter<'a, W> { impl<'a, W: Write> PdfExporter<'a, W> {
/// Prepare the export. Only once [`ExportProcess::write`] is called the
/// writing really happens.
fn new( fn new(
layouts: &'a MultiLayout, layouts: &'a MultiLayout,
loader: &'a SharedFontLoader, loader: &'a SharedFontLoader,
target: W, target: W,
) -> io::Result<PdfExporter<'a, W>> { ) -> io::Result<Self> {
let (to_pdf, to_fontdock) = remap_fonts(layouts); let (to_pdf, to_fontdock) = remap_fonts(layouts);
let offsets = calculate_offsets(layouts.len(), to_pdf.len()); let offsets = calculate_offsets(layouts.len(), to_pdf.len());
Ok(PdfExporter { Ok(Self {
writer: PdfWriter::new(target), writer: PdfWriter::new(target),
layouts, layouts,
offsets, offsets,
to_pdf, to_pdf,
to_fontdock, to_layout: to_fontdock,
loader, loader,
}) })
} }
/// Write everything (writing entry point).
fn write(&mut self) -> io::Result<usize> { fn write(&mut self) -> io::Result<usize> {
self.writer.write_header(Version::new(1, 7))?; self.writer.write_header(Version::new(1, 7))?;
self.write_preface()?; self.write_preface()?;
@ -89,14 +87,13 @@ impl<'a, W: Write> PdfExporter<'a, W> {
Ok(self.writer.written()) Ok(self.writer.written())
} }
/// Write the document catalog and page tree.
fn write_preface(&mut self) -> io::Result<()> { fn write_preface(&mut self) -> io::Result<()> {
// The document catalog. // The document catalog.
self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?; self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?;
// The font resources. // The font resources.
let start = self.offsets.fonts.0; 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)) 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). // 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(ids(self.offsets.contents))
.zip(self.layouts); .zip(self.layouts)
{
for ((page_id, content_id), page) in iter {
let rect = Rect::new( let rect = Rect::new(
0.0, 0.0,
0.0, 0.0,
@ -132,7 +128,6 @@ impl<'a, W: Write> PdfExporter<'a, W> {
Ok(()) Ok(())
} }
/// Write the contents of all pages.
fn write_pages(&mut self) -> io::Result<()> { fn write_pages(&mut self) -> io::Result<()> {
for (id, page) in ids(self.offsets.contents).zip(self.layouts) { for (id, page) in ids(self.offsets.contents).zip(self.layouts) {
self.write_page(id, &page)?; self.write_page(id, &page)?;
@ -140,11 +135,11 @@ impl<'a, W: Write> PdfExporter<'a, W> {
Ok(()) Ok(())
} }
/// Write the content of a page.
fn write_page(&mut self, id: u32, page: &BoxLayout) -> io::Result<()> { 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(); 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 face = FaceId::MAX;
let mut size = 0.0; 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 x = Length::raw(pos.x).as_pt();
let y = Length::raw(page.size.y - pos.y - size).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.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(()) Ok(())
} }
/// Write all the fonts.
fn write_fonts(&mut self) -> io::Result<()> { fn write_fonts(&mut self) -> io::Result<()> {
let mut id = self.offsets.fonts.0; 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 loader = self.loader.borrow();
let face = loader.get_loaded(face_id); 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::SYMBOLIC);
flags.insert(FontFlags::SMALL_CAP); 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. // Write the base font object referencing the CID font.
self.writer.write_obj( self.writer.write_obj(
id, id,
@ -234,12 +245,6 @@ impl<'a, W: Write> PdfExporter<'a, W> {
.to_unicode(id + 3), .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. // Write the CID font referencing the font descriptor.
self.writer.write_obj( self.writer.write_obj(
id + 1, id + 1,
@ -252,8 +257,7 @@ impl<'a, W: Write> PdfExporter<'a, W> {
.widths(vec![WidthRecord::Start(0, widths)]), .widths(vec![WidthRecord::Start(0, widths)]),
)?; )?;
// Write the font descriptor (contains the global information about // Write the font descriptor (contains metrics about the font).
// the font).
self.writer.write_obj(id + 2, self.writer.write_obj(id + 2,
FontDescriptor::new(base_font, flags, italic_angle) FontDescriptor::new(base_font, flags, italic_angle)
.font_bbox(bbox) .font_bbox(bbox)
@ -264,25 +268,15 @@ impl<'a, W: Write> PdfExporter<'a, W> {
.font_file_2(id + 4) .font_file_2(id + 4)
)?; )?;
let mut mapping = vec![]; // Write the CMap, which maps glyph ids back to unicode codepoints
for subtable in face.character_mapping_subtables() { // to enable copying out of the PDF.
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.
self.writer.write_obj(id + 3, &CMap::new( self.writer.write_obj(id + 3, &CMap::new(
"Custom", "Custom",
system_info, system_info,
mapping, mapping,
))?; ))?;
// Finally write the subsetted font bytes. // Write the face's bytes.
self.writer.write_obj(id + 4, &FontStream::new(face.data()))?; self.writer.write_obj(id + 4, &FontStream::new(face.data()))?;
id += NUM_OBJECTS_PER_FONT; 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: /// 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) /// - 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<FaceId, usize>, Vec<FaceId>) { fn remap_fonts(layouts: &MultiLayout) -> (HashMap<FaceId, usize>, Vec<FaceId>) {
let mut to_pdf = HashMap::new(); 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 // We want to find out which font faces are used at all. To do that, look at
// text element to find out which font is uses. // each text element to find out which face is uses.
for layout in layouts { for layout in layouts {
for (_, element) in &layout.elements.0 { for (_, element) in &layout.elements.0 {
let LayoutElement::Text(shaped) = element; let LayoutElement::Text(shaped) = element;
to_pdf.entry(shaped.face).or_insert_with(|| { to_pdf.entry(shaped.face).or_insert_with(|| {
let next_id = to_fontdock.len(); let next_id = to_layout.len();
to_fontdock.push(shaped.face); to_layout.push(shaped.face);
next_id 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. /// cross-reference them. Therefore, we calculate the indices in the beginning.
fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets { fn calculate_offsets(layout_count: usize, font_count: usize) -> Offsets {
let catalog = 1; let catalog = 1;
@ -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<Item = Ref> {
fn ids((start, end): (Ref, Ref)) -> impl Iterator<Item=Ref> { start..=end
start ..= end
} }

View File

@ -3,14 +3,15 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
use fontdock::{ContainsChar, FaceFromVec, FontLoader, FontProvider};
use ttf_parser::Face; use ttf_parser::Face;
use fontdock::{FontLoader, FontProvider, ContainsChar, FaceFromVec};
/// A referenced-count shared font loader backed by a dynamic provider. /// A referenced-count shared font loader backed by a dynamic provider.
pub type SharedFontLoader = Rc<RefCell<FontLoader<Box<DynProvider>>>>; pub type SharedFontLoader = Rc<RefCell<FontLoader<Box<DynProvider>>>>;
/// The dynamic font provider type backing the font loader. /// The dynamic font provider type backing the font loader.
pub type DynProvider = dyn FontProvider<Face=OwnedFace>; pub type DynProvider = dyn FontProvider<Face = OwnedFace>;
/// An owned font face. /// An owned font face.
pub struct OwnedFace { pub struct OwnedFace {
@ -18,6 +19,13 @@ pub struct OwnedFace {
face: Face<'static>, face: Face<'static>,
} }
impl OwnedFace {
/// The raw face data.
pub fn data(&self) -> &[u8] {
&self.data
}
}
impl FaceFromVec for OwnedFace { impl FaceFromVec for OwnedFace {
fn from_vec(vec: Vec<u8>, i: u32) -> Option<Self> { fn from_vec(vec: Vec<u8>, i: u32) -> Option<Self> {
// The vec's location is stable in memory since we don't touch it and // 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()) std::slice::from_raw_parts(vec.as_ptr(), vec.len())
}; };
Some(OwnedFace { Some(Self {
data: vec, data: vec,
face: Face::from_slice(slice, i).ok()?, face: Face::from_slice(slice, i).ok()?,
}) })
} }
} }
impl OwnedFace {
/// The raw face data.
pub fn data(&self) -> &[u8] {
&self.data
}
}
impl ContainsChar for OwnedFace { impl ContainsChar for OwnedFace {
fn contains_char(&self, c: char) -> bool { fn contains_char(&self, c: char) -> bool {
self.glyph_index(c).is_some() self.glyph_index(c).is_some()

View File

@ -1,20 +1,20 @@
//! Tools for building custom functions. //! 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. /// Useful things for creating functions.
pub mod prelude { pub mod prelude {
pub use crate::layout::prelude::*; pub use crate::layout::prelude::*;
pub use crate::layout::Command::{self, *}; pub use crate::layout::Command::{self, *};
pub use crate::syntax::prelude::*;
pub use crate::style::*; 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<T>: Sized { pub trait OptionExt<T>: Sized {
/// Calls `f` with `val` if this is `Some(val)`. /// Calls `f` with `val` if this is `Some(val)`.
fn with(self, f: impl FnOnce(T)); fn with(self, f: impl FnOnce(T));
@ -62,8 +62,8 @@ pub fn expect_no_body(body: Option<Spanned<&str>>, f: &mut Feedback) {
/// Implement a custom function concisely. /// Implement a custom function concisely.
/// ///
/// # Examples /// # Examples
/// Look at the source code of the [`library`](crate::library) module for /// Look at the source code of the `library` module for examples on how the
/// examples on how the macro works. /// macro works.
#[macro_export] #[macro_export]
macro_rules! function { macro_rules! function {
// Entry point. // Entry point.
@ -85,7 +85,7 @@ macro_rules! function {
// Parse trait. // Parse trait.
(@parse($($a:tt)*) parse(default) $($r:tt)*) => { (@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)* ) => { (@parse($($a:tt)*) parse($h:ident, $b:ident, $c:ident, $f:ident) $($r:tt)* ) => {
function!(@parse($($a)*) parse($h, $b, $c, $f, _metadata) $($r)*); 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)] mut call: $crate::syntax::parsing::FuncCall,
#[allow(unused)] $state: &$crate::syntax::parsing::ParseState, #[allow(unused)] $state: &$crate::syntax::parsing::ParseState,
#[allow(unused)] $metadata: Self::Meta, #[allow(unused)] $metadata: Self::Meta,
) -> $crate::Pass<Self> where Self: Sized { ) -> $crate::Pass<Self>
where
Self: Sized,
{
let mut feedback = $crate::Feedback::new(); let mut feedback = $crate::Feedback::new();
#[allow(unused)] let $header = &mut call.header; #[allow(unused)] let $header = &mut call.header;
#[allow(unused)] let $body = call.body; #[allow(unused)] let $body = call.body;

View File

@ -16,32 +16,24 @@ pub struct Value2<T> {
impl<T: Clone> Value2<T> { impl<T: Clone> Value2<T> {
/// Create a new 2D-value from two values. /// Create a new 2D-value from two values.
pub fn new(x: T, y: T) -> Value2<T> { 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. /// Create a new 2D-value with `x` set to a value and `y` to default.
pub fn with_x(x: T) -> Value2<T> where T: Default { pub fn with_x(x: T) -> Self where T: Default {
Value2 { x, y: T::default() } Self { x, y: T::default() }
} }
/// Create a new 2D-value with `y` set to a value and `x` to default. /// Create a new 2D-value with `y` set to a value and `x` to default.
pub fn with_y(y: T) -> Value2<T> where T: Default { pub fn with_y(y: T) -> Self where T: Default {
Value2 { x: T::default(), y } Self { 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<T> 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<T> where T: Default {
Value2::with_y(v).generalized(axes)
} }
/// Create a 2D-value with `x` and `y` set to the same value `s`. /// Create a 2D-value with `x` and `y` set to the same value `s`.
pub fn with_all(s: T) -> Value2<T> { Value2 { x: s.clone(), y: s } } pub fn with_all(s: T) -> Self {
Self { x: s.clone(), y: s }
}
/// Get the specificed component. /// Get the specificed component.
pub fn get(self, axis: SpecAxis) -> T { pub fn get(self, axis: SpecAxis) -> T {
@ -83,16 +75,16 @@ impl<T: Clone> Value2<T> {
/// axes, that is: /// axes, that is:
/// - `x` describes the primary axis instead of the horizontal one. /// - `x` describes the primary axis instead of the horizontal one.
/// - `y` describes the secondary axis instead of the vertical one. /// - `y` describes the secondary axis instead of the vertical one.
pub fn generalized(self, axes: LayoutAxes) -> Value2<T> { pub fn generalized(self, axes: LayoutAxes) -> Self {
match axes.primary.axis() { match axes.primary.axis() {
Horizontal => self, 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 /// Returns the specialized version of this generalized Size2D (inverse to
/// `generalized`). /// `generalized`).
pub fn specialized(self, axes: LayoutAxes) -> Value2<T> { pub fn specialized(self, axes: LayoutAxes) -> Self {
// In fact, generalized is its own inverse. For reasons of clarity // In fact, generalized is its own inverse. For reasons of clarity
// at the call site, we still have this second function. // at the call site, we still have this second function.
self.generalized(axes) self.generalized(axes)
@ -104,7 +96,7 @@ impl<T: Clone> Value2<T> {
} }
} }
impl<T> Debug for Value2<T> where T: Debug { impl<T: Debug> Debug for Value2<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_list() f.debug_list()
.entry(&self.x) .entry(&self.x)
@ -118,16 +110,16 @@ pub type Size = Value2<f64>;
impl Size { impl Size {
/// The zeroed 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 /// Whether the given size fits into this one, that is, both coordinate
/// values are smaller or equal. /// 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 self.x >= other.x && self.y >= other.y
} }
/// Return a size padded by the paddings of the given box. /// 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 { Size {
x: self.x + padding.left + padding.right, x: self.x + padding.left + padding.right,
y: self.y + padding.top + padding.bottom, y: self.y + padding.top + padding.bottom,
@ -135,7 +127,7 @@ impl Size {
} }
/// Return a size reduced by the paddings of the given box. /// 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 { Size {
x: self.x - padding.left - padding.right, x: self.x - padding.left - padding.right,
y: self.y - padding.top - padding.bottom, 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 /// This assumes the size to be generalized such that `x` corresponds to the
/// primary axis. /// primary axis.
pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Size { pub fn anchor(self, align: LayoutAlign, axes: LayoutAxes) -> Self {
Size { Size {
x: anchor(self.x, align.primary, axes.primary), x: anchor(self.x, align.primary, axes.primary),
y: anchor(self.y, align.secondary, axes.secondary), y: anchor(self.y, align.secondary, axes.secondary),
@ -189,17 +181,17 @@ pub struct Value4<T> {
impl<T: Clone> Value4<T> { impl<T: Clone> Value4<T> {
/// Create a new box from four sizes. /// Create a new box from four sizes.
pub fn new(left: T, top: T, right: T, bottom: T) -> Value4<T> { pub fn new(left: T, top: T, right: T, bottom: T) -> Self {
Value4 { left, top, right, bottom } Value4 { left, top, right, bottom }
} }
/// Create a box with all four fields set to the same value `s`. /// Create a box with all four fields set to the same value `s`.
pub fn with_all(value: T) -> Value4<T> { pub fn with_all(value: T) -> Self {
Value4 { Value4 {
left: value.clone(), left: value.clone(),
top: value.clone(), top: value.clone(),
right: value.clone(), right: value.clone(),
bottom: value bottom: value,
} }
} }
@ -213,7 +205,7 @@ impl<T: Clone> Value4<T> {
} }
match dir { match dir {
LTT => &mut self.left, LTR => &mut self.left,
RTL => &mut self.right, RTL => &mut self.right,
TTB => &mut self.top, TTB => &mut self.top,
BTT => &mut self.bottom, BTT => &mut self.bottom,
@ -224,18 +216,6 @@ impl<T: Clone> Value4<T> {
pub fn set_all(&mut self, value: T) { pub fn set_all(&mut self, value: T) {
*self = Value4::with_all(value); *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. /// A length in four dimensions.

View File

@ -1,19 +1,20 @@
//! The elements layouts are composed of. //! Basic building blocks of layouts.
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use ttf_parser::GlyphId;
use fontdock::FaceId; use fontdock::FaceId;
use ttf_parser::GlyphId;
use crate::geom::Size; use crate::geom::Size;
/// A sequence of positioned layout elements. /// A collection of absolutely positioned layout elements.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct LayoutElements(pub Vec<(Size, LayoutElement)>); pub struct LayoutElements(pub Vec<(Size, LayoutElement)>);
impl LayoutElements { impl LayoutElements {
/// Create an empty sequence. /// Create an new empty collection.
pub fn new() -> Self { pub fn new() -> Self {
LayoutElements(vec![]) Self(vec![])
} }
/// Add an element at a position. /// Add an element at a position.
@ -21,7 +22,9 @@ impl LayoutElements {
self.0.push((pos, element)); 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) { pub fn extend_offset(&mut self, offset: Size, more: Self) {
for (subpos, element) in more.0 { for (subpos, element) in more.0 {
self.0.push((subpos + offset, element)); self.0.push((subpos + offset, element));
@ -29,16 +32,9 @@ impl LayoutElements {
} }
} }
impl Default for LayoutElements { /// A layout element, the basic building block layouts are composed of.
fn default() -> Self {
Self::new()
}
}
/// A layout element, which is the basic building block layouts are composed of.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum LayoutElement { pub enum LayoutElement {
/// Shaped text.
Text(Shaped), Text(Shaped),
} }
@ -48,14 +44,17 @@ pub struct Shaped {
pub text: String, pub text: String,
pub face: FaceId, pub face: FaceId,
pub glyphs: Vec<GlyphId>, pub glyphs: Vec<GlyphId>,
/// The horizontal offsets of the glyphs with the same indices. Vertical
/// offets are not yet supported.
pub offsets: Vec<f64>, pub offsets: Vec<f64>,
/// The font size.
pub size: f64, pub size: f64,
} }
impl Shaped { impl Shaped {
/// Create an empty shape run. /// Create a new shape run with empty `text`, `glyphs` and `offsets`.
pub fn new(face: FaceId, size: f64) -> Shaped { pub fn new(face: FaceId, size: f64) -> Self {
Shaped { Self {
text: String::new(), text: String::new(),
face, face,
glyphs: vec![], glyphs: vec![],
@ -65,12 +64,11 @@ impl Shaped {
} }
/// Encode the glyph ids into a big-endian byte buffer. /// Encode the glyph ids into a big-endian byte buffer.
pub fn encode_glyphs(&self) -> Vec<u8> { pub fn encode_glyphs_be(&self) -> Vec<u8> {
const BYTES_PER_GLYPH: usize = 2; let mut bytes = Vec::with_capacity(2 * self.glyphs.len());
let mut bytes = Vec::with_capacity(BYTES_PER_GLYPH * self.glyphs.len()); for &GlyphId(g) in &self.glyphs {
for g in &self.glyphs { bytes.push((g >> 8) as u8);
bytes.push((g.0 >> 8) as u8); bytes.push((g & 0xff) as u8);
bytes.push((g.0 & 0xff) as u8);
} }
bytes bytes
} }

View File

@ -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 //! Along the primary axis, the boxes are laid out next to each other as long as
//! fit into a line. When a line break is necessary, the line is finished and a //! they fit into a line. When necessary, a line break is inserted and the new
//! new line is started offset on the secondary axis by the height of previous //! line is offset along the secondary axis by the height of the previous line
//! line and the extra line spacing. //! plus extra line spacing.
//! //!
//! Internally, the line layouter uses a stack layouter to arrange the finished //! Internally, the line layouter uses a stack layouter to stack the finished
//! lines. //! lines on top of each.
use super::stack::{StackLayouter, StackContext}; use super::stack::{StackContext, StackLayouter};
use super::*; use super::*;
/// Performs the line layouting. /// Performs the line layouting.
#[derive(Debug)]
pub struct LineLayouter { pub struct LineLayouter {
/// The context for layouting.
ctx: LineContext, ctx: LineContext,
/// The underlying stack layouter.
stack: StackLayouter, stack: StackLayouter,
/// The currently written line. /// The in-progress line.
run: LineRun, run: LineRun,
} }
/// The context for line layouting. /// The context for line layouting.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LineContext { pub struct LineContext {
/// The spaces to layout in. /// The spaces to layout into.
pub spaces: LayoutSpaces, pub spaces: LayoutSpaces,
/// The initial layouting axes, which can be updated by the /// The initial layouting axes, which can be updated through `set_axes`.
/// [`LineLayouter::set_axes`] method.
pub axes: LayoutAxes, pub axes: LayoutAxes,
/// Which alignment to set on the resulting layout. This affects how it will /// The alignment of the _resulting_ layout. This does not effect the line
/// be positioned in a parent box. /// layouting itself, but rather how the finished layout will be positioned
/// in a parent layout.
pub align: LayoutAlign, 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, pub repeat: bool,
/// The line spacing. /// The spacing to be inserted between each pair of lines.
pub line_spacing: f64, pub line_spacing: f64,
} }
/// A line run is a sequence of boxes with the same alignment that are arranged /// A sequence of boxes with the same alignment. A real line can consist of
/// in a line. A real line can consist of multiple runs with different /// multiple runs with different alignments.
/// alignments.
#[derive(Debug)]
struct LineRun { struct LineRun {
/// The so-far accumulated layouts in the line. /// The so-far accumulated items of the run.
layouts: Vec<(f64, BoxLayout)>, layouts: Vec<(f64, BoxLayout)>,
/// The width and maximal height of the line. /// The summed width and maximal height of the run.
size: Size, size: Size,
/// The alignment of all layouts in the line. /// The alignment of all layouts in the line.
/// ///
/// When a new run is created the alignment is yet to be determined. Once a /// When a new run is created the alignment is yet to be determined and
/// layout is added, it is decided which alignment the run has and all /// `None` as such. Once a layout is added, its alignment decides the
/// further elements of the run must have this alignment. /// alignment for the whole run.
align: Option<LayoutAlign>, align: Option<LayoutAlign>,
/// If another line run with different alignment already took up some space /// The amount of space left by another run on the same line or `None` if
/// of the line, this run has less space and how much is stored here. /// this is the only run so far.
usable: Option<f64>, usable: Option<f64>,
/// 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, last_spacing: LastSpacing,
} }
impl LineLayouter { impl LineLayouter {
/// Create a new line layouter. /// Create a new line layouter.
pub fn new(ctx: LineContext) -> LineLayouter { pub fn new(ctx: LineContext) -> Self {
LineLayouter { Self {
stack: StackLayouter::new(StackContext { stack: StackLayouter::new(StackContext {
spaces: ctx.spaces.clone(), spaces: ctx.spaces.clone(),
axes: ctx.axes, 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) { pub fn add(&mut self, layout: BoxLayout) {
let axes = self.ctx.axes; let axes = self.ctx.axes;
if let Some(align) = self.run.align { if let Some(align) = self.run.align {
if layout.align.secondary != align.secondary { if layout.align.secondary != align.secondary {
// TODO: Issue warning for non-fitting alignment in // TODO: Issue warning for non-fitting alignment in
// non-repeating context. // non-repeating context.
let fitting = self.stack.is_fitting_alignment(layout.align); let fitting = self.stack.is_fitting_alignment(layout.align);
if !fitting && self.ctx.repeat { if !fitting && self.ctx.repeat {
self.finish_space(true); self.finish_space(true);
@ -92,7 +89,6 @@ impl LineLayouter {
} }
} else if layout.align.primary < align.primary { } else if layout.align.primary < align.primary {
self.finish_line(); self.finish_line();
} else if layout.align.primary > align.primary { } else if layout.align.primary > align.primary {
let mut rest_run = LineRun::new(); let mut rest_run = LineRun::new();
@ -137,25 +133,24 @@ impl LineLayouter {
self.run.last_spacing = LastSpacing::None; 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) { pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts { for layout in layouts {
self.add(layout); 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 /// This specifies how much more would fit before a line break would be
/// issued. /// needed.
fn usable(&self) -> Size { 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); let mut usable = self.stack.usable().generalized(self.ctx.axes);
// If this is a alignment-continuing line, we override the primary // If there was another run already, override the stack's size.
// usable size.
if let Some(primary) = self.run.usable { if let Some(primary) = self.run.usable {
usable.x = primary; usable.x = primary;
} }
@ -164,18 +159,17 @@ impl LineLayouter {
usable 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) { pub fn add_primary_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
match kind { match kind {
// A hard space is simply an empty box.
SpacingKind::Hard => { SpacingKind::Hard => {
spacing = spacing.min(self.usable().x); spacing = spacing.min(self.usable().x);
self.run.size.x += spacing; self.run.size.x += spacing;
self.run.last_spacing = LastSpacing::Hard; self.run.last_spacing = LastSpacing::Hard;
} }
// A soft space is cached if it is not consumed by a hard space or // A soft space is cached since it might be consumed by a hard
// previous soft space with higher level. // spacing.
SpacingKind::Soft(level) => { SpacingKind::Soft(level) => {
let consumes = match self.run.last_spacing { let consumes = match self.run.last_spacing {
LastSpacing::None => true, 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) { pub fn add_secondary_spacing(&mut self, spacing: f64, kind: SpacingKind) {
self.finish_line_if_not_empty(); self.finish_line_if_not_empty();
self.stack.add_spacing(spacing, kind) self.stack.add_spacing(spacing, kind)
} }
/// Update the layouting axes used by this layouter. /// Update the layouting axes.
pub fn set_axes(&mut self, axes: LayoutAxes) { pub fn set_axes(&mut self, axes: LayoutAxes) {
self.finish_line_if_not_empty(); self.finish_line_if_not_empty();
self.ctx.axes = axes; self.ctx.axes = axes;
self.stack.set_axes(axes) self.stack.set_axes(axes)
} }
/// Update the layouting spaces to use. /// Update the layouting spaces.
/// ///
/// If `replace_empty` is true, the current space is replaced if there are /// If `replace_empty` is true, the current space is replaced if there are
/// no boxes laid into it yet. Otherwise, only the followup spaces are /// no boxes laid out into it yet. Otherwise, the followup spaces are
/// replaced. /// replaced.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
self.stack.set_spaces(spaces, replace_empty && self.line_is_empty()); self.stack.set_spaces(spaces, replace_empty && self.line_is_empty());
@ -217,13 +211,11 @@ impl LineLayouter {
self.ctx.line_spacing = line_spacing; self.ctx.line_spacing = line_spacing;
} }
/// The remaining inner layout spaces. Inner means, that padding is already /// The remaining inner spaces. If something is laid out into these spaces,
/// subtracted and the spaces are unexpanding. This can be used to signal /// it will fit into this layouter's underlying stack.
/// a function how much space it has to layout itself.
pub fn remaining(&self) -> LayoutSpaces { pub fn remaining(&self) -> LayoutSpaces {
let mut spaces = self.stack.remaining(); let mut spaces = self.stack.remaining();
*spaces[0].size.secondary_mut(self.ctx.axes) *spaces[0].size.secondary_mut(self.ctx.axes) -= self.run.size.y;
-= self.run.size.y;
spaces spaces
} }
@ -232,13 +224,13 @@ impl LineLayouter {
self.run.size == Size::ZERO && self.run.layouts.is_empty() 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 { pub fn finish(mut self) -> MultiLayout {
self.finish_line_if_not_empty(); self.finish_line_if_not_empty();
self.stack.finish() self.stack.finish()
} }
/// Finish the currently active space and start a new one. /// Finish the active space and start a new one.
/// ///
/// At the top level, this is a page break. /// At the top level, this is a page break.
pub fn finish_space(&mut self, hard: bool) { pub fn finish_space(&mut self, hard: bool) {
@ -246,7 +238,7 @@ impl LineLayouter {
self.stack.finish_space(hard) 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) { pub fn finish_line(&mut self) {
let mut elements = LayoutElements::new(); let mut elements = LayoutElements::new();
@ -265,9 +257,8 @@ impl LineLayouter {
self.stack.add(BoxLayout { self.stack.add(BoxLayout {
size: self.run.size.specialized(self.ctx.axes), size: self.run.size.specialized(self.ctx.axes),
align: self.run.align align: self.run.align.unwrap_or(LayoutAlign::new(Start, Start)),
.unwrap_or(LayoutAlign::new(Start, Start)), elements
elements
}); });
self.run = LineRun::new(); self.run = LineRun::new();
@ -275,7 +266,6 @@ impl LineLayouter {
self.stack.add_spacing(self.ctx.line_spacing, SpacingKind::LINE); 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) { fn finish_line_if_not_empty(&mut self) {
if !self.line_is_empty() { if !self.line_is_empty() {
self.finish_line() self.finish_line()
@ -284,8 +274,8 @@ impl LineLayouter {
} }
impl LineRun { impl LineRun {
fn new() -> LineRun { fn new() -> Self {
LineRun { Self {
layouts: vec![], layouts: vec![],
size: Size::ZERO, size: Size::ZERO,
align: None, align: None,

View File

@ -1,37 +1,37 @@
//! Layouting types and engines. //! Layouting of syntax trees into box layouts.
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::*;
pub mod elements; pub mod elements;
pub mod line; pub mod line;
pub mod primitive; pub mod primitive;
pub mod stack; pub mod stack;
pub mod text; pub mod text;
pub mod tree; mod tree;
pub use primitive::*;
/// Basic types used across the layouting engine. /// Basic types used across the layouting engine.
pub mod prelude { pub mod prelude {
pub use super::layout;
pub use super::primitive::*; pub use super::primitive::*;
pub use super::layout;
pub use Dir::*; pub use Dir::*;
pub use GenAxis::*;
pub use SpecAxis::*;
pub use GenAlign::*; pub use GenAlign::*;
pub use GenAxis::*;
pub use SpecAlign::*; 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. /// A collection of layouts.
pub type MultiLayout = Vec<BoxLayout>; pub type MultiLayout = Vec<BoxLayout>;
@ -40,47 +40,41 @@ pub type MultiLayout = Vec<BoxLayout>;
pub struct BoxLayout { pub struct BoxLayout {
/// The size of the box. /// The size of the box.
pub size: Size, pub size: Size,
/// How to align this layout in a parent container. /// How to align this box in a parent container.
pub align: LayoutAlign, pub align: LayoutAlign,
/// The elements composing this layout. /// The elements composing this layout.
pub elements: LayoutElements, pub elements: LayoutElements,
} }
/// Layouting of elements. /// Comamnd-based layout.
#[async_trait(?Send)] #[async_trait(?Send)]
pub trait Layout { pub trait Layout {
/// Layout self into a sequence of layouting commands. /// Create a sequence of layouting commands to execute.
async fn layout<'a>(&'a self, _: LayoutContext<'_>) -> Pass<Commands<'a>>; async fn layout<'a>(&'a self, ctx: LayoutContext<'_>) -> Pass<Commands<'a>>;
}
/// Layout a syntax tree into a list of boxes.
pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_>) -> Pass<MultiLayout> {
let mut layouter = TreeLayouter::new(ctx);
layouter.layout_tree(tree).await;
layouter.finish()
} }
/// The context for layouting. /// The context for layouting.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LayoutContext<'a> { pub struct LayoutContext<'a> {
/// The font loader to retrieve fonts from when typesetting text /// The font loader to query fonts from when typesetting text.
/// using [`layout_text`].
pub loader: &'a SharedFontLoader, pub loader: &'a SharedFontLoader,
/// The style for pages and text. /// The style for pages and text.
pub style: &'a LayoutStyle, 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, pub base: Size,
/// The spaces to layout in. /// The spaces to layout into.
pub spaces: LayoutSpaces, 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, pub repeat: bool,
/// The initial axes along which content is laid out. /// The axes along which content is laid out.
pub axes: LayoutAxes, 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, pub align: LayoutAlign,
/// Whether the layout that is to be created will be nested in a parent /// Whether this layouting process is the root page-building process.
/// container. pub root: bool,
pub nested: bool,
} }
/// A collection of layout spaces. /// A collection of layout spaces.
@ -89,17 +83,17 @@ pub type LayoutSpaces = Vec<LayoutSpace>;
/// The space into which content is laid out. /// The space into which content is laid out.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub struct LayoutSpace { pub struct LayoutSpace {
/// The maximum size of the box to layout in. /// The maximum size of the rectangle to layout into.
pub size: Size, pub size: Size,
/// Padding that should be respected on each side. /// Padding that should be respected on each side.
pub padding: Margins, pub padding: Margins,
/// Whether to expand the size of the resulting layout to the full size of /// 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, pub expansion: LayoutExpansion,
} }
impl LayoutSpace { 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)`. /// `(padding.left, padding.top)`.
pub fn start(&self) -> Size { pub fn start(&self) -> Size {
Size::new(self.padding.left, self.padding.top) Size::new(self.padding.left, self.padding.top)
@ -110,9 +104,10 @@ impl LayoutSpace {
self.size.unpadded(self.padding) self.size.unpadded(self.padding)
} }
/// A layout space without padding and size reduced by the padding. /// The inner layout space with size reduced by the padding, zero padding of
pub fn usable_space(&self) -> LayoutSpace { /// its own and no layout expansion.
LayoutSpace { pub fn inner(&self) -> Self {
Self {
size: self.usable(), size: self.usable(),
padding: Margins::ZERO, padding: Margins::ZERO,
expansion: LayoutExpansion::new(false, false), expansion: LayoutExpansion::new(false, false),
@ -123,34 +118,33 @@ impl LayoutSpace {
/// A sequence of layouting commands. /// A sequence of layouting commands.
pub type Commands<'a> = Vec<Command<'a>>; pub type Commands<'a> = Vec<Command<'a>>;
/// Commands issued to the layouting engine by trees. /// Commands executable by the layouting engine.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Command<'a> { pub enum Command<'a> {
/// Layout the given tree in the current context (i.e. not nested). The /// 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, /// 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 /// 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 /// 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), LayoutSyntaxTree(&'a SyntaxTree),
/// Add a already computed layout. /// Add a finished layout.
Add(BoxLayout), Add(BoxLayout),
/// Add multiple layouts, one after another. This is equivalent to multiple /// Add multiple layouts, one after another. This is equivalent to multiple
/// [Add](Command::Add) commands. /// `Add` commands.
AddMultiple(MultiLayout), AddMultiple(MultiLayout),
/// Add spacing of given [kind](super::SpacingKind) along the primary or /// Add spacing of the given kind along the primary or secondary axis. The
/// secondary axis. The spacing kind defines how the spacing interacts with /// kind defines how the spacing interacts with surrounding spacing.
/// surrounding spacing.
AddSpacing(f64, SpacingKind, GenAxis), AddSpacing(f64, SpacingKind, GenAxis),
/// Start a new line. /// Start a new line.
BreakLine, BreakLine,
/// Start a new paragraph. /// Start a new paragraph.
BreakParagraph, 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). /// stays empty (since the page break is a _hard_ space break).
BreakPage, BreakPage,
@ -162,6 +156,6 @@ pub enum Command<'a> {
/// Update the alignment for future boxes added to this layouting process. /// Update the alignment for future boxes added to this layouting process.
SetAlignment(LayoutAlign), SetAlignment(LayoutAlign),
/// Update the layouting axes along which future boxes will be laid /// 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), SetAxes(LayoutAxes),
} }

View File

@ -1,29 +1,27 @@
//! Layouting primitives. //! Layouting primitives.
use std::fmt::{self, Display, Formatter}; use std::fmt::{self, Display, Formatter};
use super::prelude::*; 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAxes { pub struct LayoutAxes {
/// The primary layouting direction.
pub primary: Dir, pub primary: Dir,
/// The secondary layouting direction.
pub secondary: Dir, pub secondary: Dir,
} }
impl LayoutAxes { impl LayoutAxes {
/// Create a new instance from the two values. /// Create a new instance from the two directions.
/// ///
/// # Panics /// # 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. /// 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() { if primary.axis() == secondary.axis() {
panic!("invalid aligned axes {} and {}", primary, secondary); panic!("directions {} and {} are aligned", primary, secondary);
} }
Self { primary, secondary }
LayoutAxes { primary, secondary }
} }
/// Return the direction of the specified generic axis. /// Return the direction of the specified generic axis.
@ -46,9 +44,13 @@ impl LayoutAxes {
/// Directions along which content is laid out. /// Directions along which content is laid out.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum Dir { pub enum Dir {
LTT, /// Left to right.
LTR,
/// Right to left.
RTL, RTL,
/// Top to bottom.
TTB, TTB,
/// Bottom to top.
BTT, BTT,
} }
@ -56,34 +58,34 @@ impl Dir {
/// The specific axis this direction belongs to. /// The specific axis this direction belongs to.
pub fn axis(self) -> SpecAxis { pub fn axis(self) -> SpecAxis {
match self { match self {
LTT | RTL => Horizontal, LTR | RTL => Horizontal,
TTB | BTT => Vertical, 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 { pub fn is_positive(self) -> bool {
match self { match self {
LTT | TTB => true, LTR | TTB => true,
RTL | BTT => false, RTL | BTT => false,
} }
} }
/// The factor for this direction. /// The factor for this direction.
/// ///
/// - `1` if the direction is positive. /// - `1.0` if the direction is positive.
/// - `-1` if the direction is negative. /// - `-1.0` if the direction is negative.
pub fn factor(self) -> f64 { pub fn factor(self) -> f64 {
if self.is_positive() { 1.0 } else { -1.0 } if self.is_positive() { 1.0 } else { -1.0 }
} }
/// The inverse axis. /// The inverse direction.
pub fn inv(self) -> Dir { pub fn inv(self) -> Self {
match self { match self {
LTT => RTL, LTR => RTL,
RTL => LTT, RTL => LTR,
TTB => BTT, TTB => BTT,
BTT => TTB, BTT => TTB,
} }
@ -93,7 +95,7 @@ impl Dir {
impl Display for Dir { impl Display for Dir {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.pad(match self { f.pad(match self {
LTT => "ltr", LTR => "ltr",
RTL => "rtl", RTL => "rtl",
TTB => "ttb", TTB => "ttb",
BTT => "btt", BTT => "btt",
@ -104,9 +106,9 @@ impl Display for Dir {
/// The two generic layouting axes. /// The two generic layouting axes.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum GenAxis { pub enum GenAxis {
/// The primary axis along which words are laid out. /// The primary layouting direction along which text and lines flow.
Primary, Primary,
/// The secondary axis along which lines and paragraphs are laid out. /// The secondary layouting direction along which paragraphs grow.
Secondary, Secondary,
} }
@ -154,19 +156,17 @@ impl Display for SpecAxis {
/// Specifies where to align a layout in a parent container. /// Specifies where to align a layout in a parent container.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct LayoutAlign { pub struct LayoutAlign {
/// The alignment along the primary axis.
pub primary: GenAlign, pub primary: GenAlign,
/// The alignment along the secondary axis.
pub secondary: GenAlign, pub secondary: GenAlign,
} }
impl LayoutAlign { impl LayoutAlign {
/// Create a new instance from the two values. /// Create a new instance from the two alignments.
pub fn new(primary: GenAlign, secondary: GenAlign) -> LayoutAlign { pub fn new(primary: GenAlign, secondary: GenAlign) -> Self {
LayoutAlign { primary, secondary } 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 { pub fn get(self, axis: GenAxis) -> GenAlign {
match axis { match axis {
Primary => self.primary, 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 { pub fn get_mut(&mut self, axis: GenAxis) -> &mut GenAlign {
match axis { match axis {
Primary => &mut self.primary, 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum GenAlign { pub enum GenAlign {
Start, Start,
@ -193,7 +193,7 @@ pub enum GenAlign {
impl GenAlign { impl GenAlign {
/// The inverse alignment. /// The inverse alignment.
pub fn inv(self) -> GenAlign { pub fn inv(self) -> Self {
match self { match self {
Start => End, Start => End,
Center => Center, 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum SpecAlign { pub enum SpecAlign {
Left, Left,
@ -225,7 +225,7 @@ pub enum SpecAlign {
impl SpecAlign { impl SpecAlign {
/// The specific axis this alignment refers to. /// 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<SpecAxis> { pub fn axis(self) -> Option<SpecAxis> {
match self { match self {
Self::Left => Some(Horizontal), 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 { pub fn to_generic(self, axes: LayoutAxes) -> GenAlign {
let get = |spec: SpecAxis, align: GenAlign| { let get = |spec: SpecAxis, align: GenAlign| {
let axis = spec.to_generic(axes); let axis = spec.to_generic(axes);
@ -277,8 +277,8 @@ pub struct LayoutExpansion {
impl LayoutExpansion { impl LayoutExpansion {
/// Create a new instance from the two values. /// Create a new instance from the two values.
pub fn new(horizontal: bool, vertical: bool) -> LayoutExpansion { pub fn new(horizontal: bool, vertical: bool) -> Self {
LayoutExpansion { horizontal, vertical } Self { horizontal, vertical }
} }
/// Return the expansion value for the given specific axis. /// 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 /// Defines how spacing interacts with surrounding spacing.
/// spacing.
/// ///
/// There are two options for interaction: Hard and soft spacing. Typically, /// 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 /// hard spacing is used when a fixed amount of space needs to be inserted no
@ -317,31 +316,31 @@ pub enum SpacingKind {
impl SpacingKind { impl SpacingKind {
/// The standard spacing kind used for paragraph spacing. /// 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. /// 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. /// 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. /// 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)] #[derive(Debug, Copy, Clone, PartialEq)]
pub(crate) enum LastSpacing { pub(crate) enum LastSpacing {
/// The last item was hard spacing. /// The last item was hard spacing.
Hard, Hard,
/// The last item was soft spacing with the given width and level. /// The last item was soft spacing with the given width and level.
Soft(f64, u32), Soft(f64, u32),
/// The last item was not spacing. /// The last item wasn't spacing.
None, None,
} }
impl LastSpacing { impl LastSpacing {
/// The width of the soft space if this is a soft space or zero otherwise. /// 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 { match self {
LastSpacing::Soft(space, _) => space, LastSpacing::Soft(space, _) => space,
_ => 0.0, _ => 0.0,

View File

@ -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 //! Individual layouts can be aligned at `Start`, `Center` or `End` along both
//! these alignments are with respect to the growable layout space and not the //! axes. These alignments are with respect to the size of the finished layout
//! total possible size. //! 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.
//! This means that a later layout can have influence on the position of an
//! earlier one. Consider, for example, the following code:
//! ```typst //! ```typst
//! [align: right][A word.] //! [align: right][A word.]
//! [align: left][A sentence with a couple more words.] //! [align: left][A sentence with a couple more words.]
@ -25,38 +23,35 @@ use crate::geom::Value4;
use super::*; use super::*;
/// Performs the stack layouting. /// Performs the stack layouting.
#[derive(Debug)]
pub struct StackLayouter { pub struct StackLayouter {
/// The context for layouting.
ctx: StackContext, ctx: StackContext,
/// The output layouts.
layouts: MultiLayout, layouts: MultiLayout,
/// The currently active layout space. /// The in-progress space.
space: Space, space: Space,
} }
/// The context for stack layouting. /// The context for stack layouting.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StackContext { pub struct StackContext {
/// The spaces to layout in. /// The spaces to layout into.
pub spaces: LayoutSpaces, pub spaces: LayoutSpaces,
/// The initial layouting axes, which can be updated by the /// The initial layouting axes, which can be updated through `set_axes`.
/// [`StackLayouter::set_axes`] method.
pub axes: LayoutAxes, pub axes: LayoutAxes,
/// Which alignment to set on the resulting layout. This affects how it will /// The alignment of the _resulting_ layout. This does not effect the line
/// be positioned in a parent box. /// layouting itself, but rather how the finished layout will be positioned
/// in a parent layout.
pub align: LayoutAlign, 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, pub repeat: bool,
} }
/// A layout space composed of subspaces which can have different axes and /// A layout space composed of subspaces which can have different axes and
/// alignments. /// alignments.
#[derive(Debug)]
struct Space { struct Space {
/// The index of this space in the list of spaces. /// The index of this space in `ctx.spaces`.
index: usize, 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, hard: bool,
/// The so-far accumulated layouts. /// The so-far accumulated layouts.
layouts: Vec<(LayoutAxes, BoxLayout)>, layouts: Vec<(LayoutAxes, BoxLayout)>,
@ -66,18 +61,20 @@ struct Space {
usable: Size, usable: Size,
/// The specialized extra-needed size to affect the size at all. /// The specialized extra-needed size to affect the size at all.
extra: Size, extra: Size,
/// The rulers of a space dictate which alignments for new boxes are still /// Dictate which alignments for new boxes are still allowed and which
/// allowed and which require a new space to be started. /// require a new space to be started. For example, after an `End`-aligned
/// item, no `Start`-aligned one can follow.
rulers: Value4<GenAlign>, rulers: Value4<GenAlign>,
/// 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, last_spacing: LastSpacing,
} }
impl StackLayouter { impl StackLayouter {
/// Create a new stack layouter. /// Create a new stack layouter.
pub fn new(ctx: StackContext) -> StackLayouter { pub fn new(ctx: StackContext) -> Self {
let space = ctx.spaces[0]; let space = ctx.spaces[0];
StackLayouter { Self {
ctx, ctx,
layouts: MultiLayout::new(), layouts: MultiLayout::new(),
space: Space::new(0, true, space.usable()), space: Space::new(0, true, space.usable()),
@ -87,8 +84,8 @@ impl StackLayouter {
/// Add a layout to the stack. /// Add a layout to the stack.
pub fn add(&mut self, layout: BoxLayout) { pub fn add(&mut self, layout: BoxLayout) {
// If the alignment cannot be fitted in this space, finish it. // If the alignment cannot be fitted in this space, finish it.
// TODO: Issue warning for non-fitting alignment in // TODO: Issue warning for non-fitting alignment in non-repeating
// non-repeating context. // context.
if !self.update_rulers(layout.align) && self.ctx.repeat { if !self.update_rulers(layout.align) && self.ctx.repeat {
self.finish_space(true); self.finish_space(true);
} }
@ -116,14 +113,14 @@ impl StackLayouter {
/// Add multiple layouts to the stack. /// 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) { pub fn add_multiple(&mut self, layouts: MultiLayout) {
for layout in layouts { for layout in layouts {
self.add(layout); 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) { pub fn add_spacing(&mut self, mut spacing: f64, kind: SpacingKind) {
match kind { match kind {
// A hard space is simply an empty box. // A hard space is simply an empty box.
@ -133,11 +130,14 @@ impl StackLayouter {
let size = Size::with_y(spacing); let size = Size::with_y(spacing);
self.update_metrics(size); self.update_metrics(size);
self.space.layouts.push((self.ctx.axes, BoxLayout { self.space.layouts.push((
size: size.specialized(self.ctx.axes), self.ctx.axes,
align: LayoutAlign::new(Start, Start), BoxLayout {
elements: LayoutElements::new(), size: size.specialized(self.ctx.axes),
})); align: LayoutAlign::new(Start, Start),
elements: LayoutElements::new(),
}
));
self.space.last_spacing = LastSpacing::Hard; 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) { fn update_metrics(&mut self, added: Size) {
let axes = self.ctx.axes; let axes = self.ctx.axes;
@ -177,31 +175,29 @@ impl StackLayouter {
*self.space.usable.secondary_mut(axes) -= added.y; *self.space.usable.secondary_mut(axes) -= added.y;
} }
/// Update the rulers to account for the new layout. Returns true if a /// Returns true if a space break is necessary.
/// space break is necessary.
fn update_rulers(&mut self, align: LayoutAlign) -> bool { fn update_rulers(&mut self, align: LayoutAlign) -> bool {
let allowed = self.is_fitting_alignment(align); let allowed = self.is_fitting_alignment(align);
if allowed { if allowed {
*self.space.rulers.get_mut(self.ctx.axes.secondary, Start) *self.space.rulers.get_mut(self.ctx.axes.secondary, Start) =
= align.secondary; align.secondary;
} }
allowed allowed
} }
/// Whether a layout with the given alignment can still be layouted in the /// Whether a layout with the given alignment can still be layouted into the
/// active space. /// active space or a space break is necessary.
pub fn is_fitting_alignment(&mut self, align: LayoutAlign) -> bool { 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.primary, align.primary)
&& self.is_fitting_axis(self.ctx.axes.secondary, align.secondary) && 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 { 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, 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) { pub fn set_axes(&mut self, axes: LayoutAxes) {
// Forget the spacing because it is not relevant anymore. // Forget the spacing because it is not relevant anymore.
if axes.secondary != self.ctx.axes.secondary { if axes.secondary != self.ctx.axes.secondary {
@ -211,10 +207,10 @@ impl StackLayouter {
self.ctx.axes = axes; 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 /// 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. /// replaced.
pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) { pub fn set_spaces(&mut self, spaces: LayoutSpaces, replace_empty: bool) {
if replace_empty && self.space_is_empty() { if replace_empty && self.space_is_empty() {
@ -234,13 +230,13 @@ impl StackLayouter {
if space.usable().fits(size) { if space.usable().fits(size) {
self.finish_space(true); self.finish_space(true);
self.start_space(start + index, true); self.start_space(start + index, true);
return; break;
} }
} }
} }
/// The remaining unpadded, unexpanding spaces. If a function is laid out /// The remaining inner spaces. If something is laid out into these spaces,
/// into these spaces, it will fit into this stack. /// it will fit into this stack.
pub fn remaining(&self) -> LayoutSpaces { pub fn remaining(&self) -> LayoutSpaces {
let size = self.usable(); let size = self.usable();
@ -251,7 +247,7 @@ impl StackLayouter {
}]; }];
for space in &self.ctx.spaces[self.next_space()..] { for space in &self.ctx.spaces[self.next_space()..] {
spaces.push(space.usable_space()); spaces.push(space.inner());
} }
spaces spaces
@ -264,17 +260,17 @@ impl StackLayouter {
.specialized(self.ctx.axes) .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 { pub fn space_is_empty(&self) -> bool {
self.space.size == Size::ZERO && self.space.layouts.is_empty() 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 { pub fn space_is_last(&self) -> bool {
self.space.index == self.ctx.spaces.len() - 1 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 { pub fn finish(mut self) -> MultiLayout {
if self.space.hard || !self.space_is_empty() { if self.space.hard || !self.space_is_empty() {
self.finish_space(false); self.finish_space(false);
@ -282,7 +278,7 @@ impl StackLayouter {
self.layouts 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) { pub fn finish_space(&mut self, hard: bool) {
let space = self.ctx.spaces[self.space.index]; 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 // 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 // the usable space for following layouts at it's origin by its
// extent along the secondary axis. // extent along the secondary axis.
*bound.get_mut(axes.secondary, Start) *bound.get_mut(axes.secondary, Start) +=
+= axes.secondary.factor() * layout.size.secondary(*axes); 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 // 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, // accumulated secondary extent of all layouts we have seen so far,
// which are the layouts after this one since we iterate reversed. // which are the layouts after this one since we iterate reversed.
*bound.get_mut(axes.secondary, End) *bound.get_mut(axes.secondary, End) -=
-= axes.secondary.factor() * extent.y; axes.secondary.factor() * extent.y;
// Then, we add this layout's secondary extent to the accumulator. // Then, we add this layout's secondary extent to the accumulator.
let size = layout.size.generalized(*axes); let size = layout.size.generalized(*axes);
@ -395,21 +391,19 @@ impl StackLayouter {
self.start_space(self.next_space(), hard) self.start_space(self.next_space(), hard)
} }
/// Start a new space with the given index.
fn start_space(&mut self, index: usize, hard: bool) { fn start_space(&mut self, index: usize, hard: bool) {
let space = self.ctx.spaces[index]; let space = self.ctx.spaces[index];
self.space = Space::new(index, hard, space.usable()); self.space = Space::new(index, hard, space.usable());
} }
/// The index of the next space.
fn next_space(&self) -> usize { fn next_space(&self) -> usize {
(self.space.index + 1).min(self.ctx.spaces.len() - 1) (self.space.index + 1).min(self.ctx.spaces.len() - 1)
} }
} }
impl Space { impl Space {
fn new(index: usize, hard: bool, usable: Size) -> Space { fn new(index: usize, hard: bool, usable: Size) -> Self {
Space { Self {
index, index,
hard, hard,
layouts: vec![], layouts: vec![],

View File

@ -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. //! The layouter picks the most suitable font for each individual character.
//! When the primary layouting axis horizontally inversed, the word is spelled //! When the primary layouting axis horizontally inversed, the word is spelled
//! backwards. Vertical word layout is not yet supported. //! backwards. Vertical word layout is not yet supported.
use ttf_parser::GlyphId;
use fontdock::{FaceId, FaceQuery, FontStyle}; use fontdock::{FaceId, FaceQuery, FontStyle};
use ttf_parser::GlyphId;
use crate::font::SharedFontLoader; use crate::font::SharedFontLoader;
use crate::geom::Size; use crate::geom::Size;
use crate::style::TextStyle; use crate::style::TextStyle;
use super::elements::{LayoutElement, Shaped}; use super::elements::{LayoutElement, Shaped};
use super::*; 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. /// Performs the text layouting.
#[derive(Debug)] #[derive(Debug)]
struct TextLayouter<'a> { struct TextLayouter<'a> {
@ -26,28 +32,24 @@ struct TextLayouter<'a> {
/// The context for text layouting. /// The context for text layouting.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct TextContext<'a> { pub struct TextContext<'a> {
/// The font loader to retrieve fonts from when typesetting text /// The font loader to retrieve fonts from when typesetting text with
/// using [`layout_text`]. /// `layout_text`.
pub loader: &'a SharedFontLoader, pub loader: &'a SharedFontLoader,
/// The style for text: Font selection with classes, weights and variants, /// The style for text: Font selection with classes, weights and variants,
/// font sizes, spacing and so on. /// font sizes, spacing and so on.
pub style: &'a TextStyle, pub style: &'a TextStyle,
/// The axes along which the word is laid out. For now, only /// The direction into which the word is laid out. For now, only horizontal
/// primary-horizontal layouting is supported. /// directions are supported.
pub axes: LayoutAxes, pub dir: Dir,
/// 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, 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> { impl<'a> TextLayouter<'a> {
/// Create a new text layouter. fn new(text: &'a str, ctx: TextContext<'a>) -> Self {
fn new(text: &'a str, ctx: TextContext<'a>) -> TextLayouter<'a> { Self {
TextLayouter {
ctx, ctx,
text, text,
shaped: Shaped::new(FaceId::MAX, ctx.style.font_size()), 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 { async fn layout(mut self) -> BoxLayout {
// If the primary axis is negative, we layout the characters reversed. // 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() { for c in self.text.chars() {
self.layout_char(c).await; 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) { async fn layout_char(&mut self, c: char) {
let (index, glyph, char_width) = match self.select_font(c).await { let (index, glyph, char_width) = match self.select_font(c).await {
Some(selected) => selected, Some(selected) => selected,
@ -115,11 +115,8 @@ impl<'a> TextLayouter<'a> {
self.width += char_width; 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)> { async fn select_font(&mut self, c: char) -> Option<(FaceId, GlyphId, f64)> {
let mut loader = self.ctx.loader.borrow_mut(); let mut loader = self.ctx.loader.borrow_mut();
let mut variant = self.ctx.style.variant; let mut variant = self.ctx.style.variant;
if self.ctx.style.bolder { if self.ctx.style.bolder {

View File

@ -1,19 +1,26 @@
//! The tree layouter layouts trees (i.e. //! Layouting of syntax trees.
//! [syntax trees](crate::syntax::SyntaxTree) and [functions](crate::func))
//! by executing commands issued by the trees.
use crate::{Pass, Feedback, DynFuture};
use crate::style::LayoutStyle; use crate::style::LayoutStyle;
use crate::syntax::decoration::Decoration; use crate::syntax::decoration::Decoration;
use crate::syntax::tree::{SyntaxTree, SyntaxNode, DynamicNode};
use crate::syntax::span::{Span, Spanned}; 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::text::{layout_text, TextContext};
use super::*; use super::*;
/// Layout a syntax tree into a collection of boxes.
pub async fn layout_tree(
tree: &SyntaxTree,
ctx: LayoutContext<'_>,
) -> Pass<MultiLayout> {
let mut layouter = TreeLayouter::new(ctx);
layouter.layout_tree(tree).await;
layouter.finish()
}
/// Performs the tree layouting. /// Performs the tree layouting.
#[derive(Debug)] struct TreeLayouter<'a> {
pub struct TreeLayouter<'a> {
ctx: LayoutContext<'a>, ctx: LayoutContext<'a>,
layouter: LineLayouter, layouter: LineLayouter,
style: LayoutStyle, style: LayoutStyle,
@ -21,9 +28,8 @@ pub struct TreeLayouter<'a> {
} }
impl<'a> TreeLayouter<'a> { impl<'a> TreeLayouter<'a> {
/// Create a new tree layouter. fn new(ctx: LayoutContext<'a>) -> Self {
pub fn new(ctx: LayoutContext<'a>) -> TreeLayouter<'a> { Self {
TreeLayouter {
layouter: LineLayouter::new(LineContext { layouter: LineLayouter::new(LineContext {
spaces: ctx.spaces.clone(), spaces: ctx.spaces.clone(),
axes: ctx.axes, axes: ctx.axes,
@ -37,15 +43,17 @@ impl<'a> TreeLayouter<'a> {
} }
} }
/// Layout a syntax tree by directly processing the nodes instead of using async fn layout_tree(&mut self, tree: &SyntaxTree) {
/// the command based architecture.
pub async fn layout_tree(&mut self, tree: &SyntaxTree) {
for node in tree { for node in tree {
self.layout_node(node).await; self.layout_node(node).await;
} }
} }
pub async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) { fn finish(self) -> Pass<MultiLayout> {
Pass::new(self.layouter.finish(), self.feedback)
}
async fn layout_node(&mut self, node: &Spanned<SyntaxNode>) {
let decorate = |this: &mut TreeLayouter, deco| { let decorate = |this: &mut TreeLayouter, deco| {
this.feedback.decorations.push(Spanned::new(deco, node.span)); this.feedback.decorations.push(Spanned::new(deco, node.span));
}; };
@ -80,7 +88,10 @@ impl<'a> TreeLayouter<'a> {
SyntaxNode::Raw(lines) => { SyntaxNode::Raw(lines) => {
// TODO: Make this more efficient. // TODO: Make this more efficient.
let fallback = self.style.text.fallback.clone(); 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(); self.style.text.fallback.flatten();
// Layout the first line. // Layout the first line.
@ -104,17 +115,15 @@ impl<'a> TreeLayouter<'a> {
} }
} }
/// Layout a node into this layouting process. async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) {
pub async fn layout_dyn(&mut self, dynamic: Spanned<&dyn DynamicNode>) { // Execute the tree's command-generating layout function.
// Execute the tree's layout function which generates the commands.
let layouted = dynamic.v.layout(LayoutContext { let layouted = dynamic.v.layout(LayoutContext {
style: &self.style, style: &self.style,
spaces: self.layouter.remaining(), spaces: self.layouter.remaining(),
nested: true, root: true,
.. self.ctx ..self.ctx
}).await; }).await;
// Add the errors generated by the tree to the error list.
self.feedback.extend_offset(layouted.feedback, dynamic.span.start); self.feedback.extend_offset(layouted.feedback, dynamic.span.start);
for command in layouted.output { for command in layouted.output {
@ -122,13 +131,6 @@ impl<'a> TreeLayouter<'a> {
} }
} }
/// Compute the finished list of boxes.
pub fn finish(self) -> Pass<MultiLayout> {
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>( fn execute_command<'r>(
&'r mut self, &'r mut self,
command: Command<'r>, command: Command<'r>,
@ -149,13 +151,13 @@ impl<'a> TreeLayouter<'a> {
BreakLine => self.layouter.finish_line(), BreakLine => self.layouter.finish_line(),
BreakParagraph => self.layout_paragraph(), BreakParagraph => self.layout_paragraph(),
BreakPage => { BreakPage => {
if self.ctx.nested { if self.ctx.root {
self.layouter.finish_space(true)
} else {
error!( error!(
@self.feedback, tree_span, @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; self.style.text = style;
} }
SetPageStyle(style) => { SetPageStyle(style) => {
if self.ctx.nested { if self.ctx.root {
error!(
@self.feedback, tree_span,
"page style cannot be changed from nested context",
);
} else {
self.style.page = style; self.style.page = style;
// The line layouter has no idea of page styles and thus we // 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), expansion: LayoutExpansion::new(true, 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) { async fn layout_text(&mut self, text: &str) {
self.layouter.add(layout_text(text, TextContext { self.layouter.add(
loader: &self.ctx.loader, layout_text(
style: &self.style.text, text,
axes: self.ctx.axes, TextContext {
align: self.ctx.align, loader: &self.ctx.loader,
}).await) 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) { fn layout_space(&mut self) {
self.layouter.add_primary_spacing( self.layouter.add_primary_spacing(
self.style.text.word_spacing(), self.style.text.word_spacing(),
@ -213,7 +218,6 @@ impl<'a> TreeLayouter<'a> {
); );
} }
/// Finish the paragraph and add paragraph spacing.
fn layout_paragraph(&mut self) { fn layout_paragraph(&mut self) {
self.layouter.add_secondary_spacing( self.layouter.add_secondary_spacing(
self.style.text.paragraph_spacing(), self.style.text.paragraph_spacing(),

View File

@ -84,7 +84,7 @@ impl Length {
} }
/// Convert this to a length with a different unit. /// 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 { Self {
val: self.val * self.unit.raw_scale() / unit.raw_scale(), val: self.val * self.unit.raw_scale() / unit.raw_scale(),
unit, unit,
@ -160,7 +160,7 @@ impl FromStr for Length {
src[..split] src[..split]
.parse::<f64>() .parse::<f64>()
.map(|val| Length::new(val, unit)) .map(|val| Self::new(val, unit))
.map_err(|_| ParseLengthError) .map_err(|_| ParseLengthError)
} }
} }

View File

@ -2,18 +2,40 @@
//! //!
//! # Steps //! # Steps
//! - **Parsing:** The parsing step first transforms a plain string into an //! - **Parsing:** The parsing step first transforms a plain string into an
//! [iterator of tokens](crate::syntax::Tokens). Then, a parser constructs a //! [iterator of tokens][tokens]. Then, a parser constructs a syntax tree from
//! syntax tree from the token stream. The structures describing the tree can //! the token stream. The structures describing the tree can be found in the
//! be found in the [syntax](crate::syntax) module. //! [syntax] module.
//! - **Layouting:** The next step is to transform the syntax tree into a //! - **Layouting:** The next step is to transform the syntax tree into a
//! portable representation of the typesetted document. Types for these can be //! portable representation of the typesetted document. Types for these can be
//! found in the [layout](crate::layout) module. A finished layout reading for //! found in the [layout] module. A finished layout ready for exporting is a
//! exporting is a [MultiLayout](crate::layout::MultiLayout) consisting of //! [`MultiLayout`] consisting of multiple boxes (or pages).
//! multiple boxes (or pages).
//! - **Exporting:** The finished layout can then be exported into a supported //! - **Exporting:** The finished layout can then be exported into a supported
//! format. Submodules for these formats are located in the //! format. Submodules for these formats are located in the [export] module.
//! [export](crate::export) module. Currently, the only supported output //! Currently, the only supported output format is [_PDF_].
//! format is [_PDF_](crate::export::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::fmt::Debug;
use std::future::Future; use std::future::Future;
@ -24,26 +46,9 @@ use crate::font::SharedFontLoader;
use crate::layout::MultiLayout; use crate::layout::MultiLayout;
use crate::style::{LayoutStyle, PageStyle, TextStyle}; use crate::style::{LayoutStyle, PageStyle, TextStyle};
use crate::syntax::decoration::Decorations; use crate::syntax::decoration::Decorations;
use crate::syntax::tree::SyntaxTree;
use crate::syntax::parsing::{parse, ParseState}; use crate::syntax::parsing::{parse, ParseState};
use crate::syntax::scope::Scope;
use crate::syntax::span::{Offset, Pos}; use crate::syntax::span::{Offset, Pos};
use crate::syntax::tree::SyntaxTree;
#[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;
/// Transforms source code into typesetted layouts. /// Transforms source code into typesetted layouts.
/// ///
@ -59,11 +64,11 @@ pub struct Typesetter {
impl Typesetter { impl Typesetter {
/// Create a new typesetter. /// Create a new typesetter.
pub fn new(loader: SharedFontLoader) -> Typesetter { pub fn new(loader: SharedFontLoader) -> Self {
Typesetter { Self {
loader, loader,
style: LayoutStyle::default(), 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), expansion: LayoutExpansion::new(true, true),
}], }],
repeat: true, repeat: true,
axes: LayoutAxes::new(LTT, TTB), axes: LayoutAxes::new(LTR, TTB),
align: LayoutAlign::new(Start, Start), align: LayoutAlign::new(Start, Start),
nested: false, root: true,
}, },
).await ).await
} }
@ -119,7 +124,7 @@ impl Typesetter {
/// A dynamic future type which allows recursive invocation of async functions /// 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 /// when used as the return type. This is also how the async trait functions
/// work internally. /// work internally.
pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output=T> + 'a>>; pub type DynFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
/// The result of some pass: Some output `T` and feedback data. /// The result of some pass: Some output `T` and feedback data.
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
@ -132,12 +137,12 @@ pub struct Pass<T> {
impl<T> Pass<T> { impl<T> Pass<T> {
/// Create a new pass from output and feedback data. /// Create a new pass from output and feedback data.
pub fn new(output: T, feedback: Feedback) -> Pass<T> { pub fn new(output: T, feedback: Feedback) -> Self {
Pass { output, feedback } Self { output, feedback }
} }
/// Map the output type and keep the feedback data. /// Map the output type and keep the feedback data.
pub fn map<F, U>(self, f: F) -> Pass<U> where F: FnOnce(T) -> U { pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Pass<U> {
Pass { Pass {
output: f(self.output), output: f(self.output),
feedback: self.feedback, feedback: self.feedback,
@ -145,10 +150,10 @@ impl<T> Pass<T> {
} }
} }
/// User feedback data accumulated during a compilation pass. /// Diagnostic and semantic syntax highlighting data.
#[derive(Debug, Default, Clone, Eq, PartialEq)] #[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Feedback { pub struct Feedback {
/// Diagnostics in the source code. /// Diagnostics about the source code.
pub diagnostics: Diagnostics, pub diagnostics: Diagnostics,
/// Decorations of the source code for semantic syntax highlighting. /// Decorations of the source code for semantic syntax highlighting.
pub decorations: Decorations, pub decorations: Decorations,
@ -156,28 +161,28 @@ pub struct Feedback {
impl Feedback { impl Feedback {
/// Create a new feedback instance without errors and decos. /// Create a new feedback instance without errors and decos.
pub fn new() -> Feedback { pub fn new() -> Self {
Feedback { Self {
diagnostics: vec![], diagnostics: vec![],
decorations: vec![], decorations: vec![],
} }
} }
/// Merged two feedbacks into one. /// 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.extend(b);
a a
} }
/// Add other feedback data to this feedback. /// Add other feedback data to this feedback.
pub fn extend(&mut self, other: Feedback) { pub fn extend(&mut self, more: Self) {
self.diagnostics.extend(other.diagnostics); self.diagnostics.extend(more.diagnostics);
self.decorations.extend(other.decorations); self.decorations.extend(more.decorations);
} }
/// Add more feedback whose spans are local and need to be offset by an /// Add more feedback whose spans are local and need to be offset by an
/// `offset` to be correct in this feedback's context. /// `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.diagnostics.extend(more.diagnostics.offset(offset));
self.decorations.extend(more.decorations.offset(offset)); self.decorations.extend(more.decorations.offset(offset));
} }

View File

@ -1,6 +1,5 @@
//! Font configuration.
use fontdock::{FontStyle, FontWeight, FontWidth}; use fontdock::{FontStyle, FontWeight, FontWidth};
use crate::length::ScaleLength; use crate::length::ScaleLength;
use super::*; use super::*;
@ -19,7 +18,6 @@ function! {
parse(header, body, state, f) { parse(header, body, state, f) {
let size = header.args.pos.get::<ScaleLength>(); let size = header.args.pos.get::<ScaleLength>();
let style = header.args.key.get::<FontStyle>("style", f); let style = header.args.key.get::<FontStyle>("style", f);
let weight = header.args.key.get::<FontWeight>("weight", f); let weight = header.args.key.get::<FontWeight>("weight", f);
let width = header.args.key.get::<FontWidth>("width", f); let width = header.args.key.get::<FontWidth>("width", f);
@ -40,7 +38,7 @@ function! {
}) })
.collect(); .collect();
FontFunc { Self {
body: parse_maybe_body(body, state, f), body: parse_maybe_body(body, state, f),
size, size,
style, style,
@ -52,30 +50,39 @@ function! {
} }
layout(self, ctx, f) { layout(self, ctx, f) {
styled(&self.body, ctx, Some(()), |t, _| { let mut text = ctx.style.text.clone();
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,
});
self.style.with(|s| t.variant.style = s); self.size.with(|s| match s {
self.weight.with(|w| t.variant.weight = w); ScaleLength::Absolute(length) => {
self.width.with(|w| t.variant.width = w); text.base_font_size = length.as_raw();
text.font_scale = 1.0;
if !self.list.is_empty() {
*t.fallback.list_mut() = self.list.iter()
.map(|s| s.to_lowercase())
.collect();
} }
ScaleLength::Scaled(scale) => text.font_scale = scale,
});
for (class, fallback) in &self.classes { self.style.with(|s| text.variant.style = s);
t.fallback.set_class_list(class.clone(), fallback.clone()); 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)],
}
} }
} }

View File

@ -1,5 +1,3 @@
//! Layout building blocks.
use crate::length::ScaleLength; use crate::length::ScaleLength;
use super::*; use super::*;
@ -13,7 +11,7 @@ function! {
} }
parse(header, body, state, f) { parse(header, body, state, f) {
BoxFunc { Self {
body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()), body: parse_maybe_body(body, state, f).unwrap_or(SyntaxTree::new()),
width: header.args.key.get::<ScaleLength>("width", f), width: header.args.key.get::<ScaleLength>("width", f),
height: header.args.key.get::<ScaleLength>("height", f), height: header.args.key.get::<ScaleLength>("height", f),
@ -21,8 +19,8 @@ function! {
} }
layout(self, ctx, f) { layout(self, ctx, f) {
ctx.repeat = false;
ctx.spaces.truncate(1); ctx.spaces.truncate(1);
ctx.repeat = false;
self.width.with(|v| { self.width.with(|v| {
let length = v.raw_scaled(ctx.base.x); let length = v.raw_scaled(ctx.base.x);
@ -51,13 +49,13 @@ function! {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct AlignFunc { pub struct AlignFunc {
body: Option<SyntaxTree>, body: Option<SyntaxTree>,
aligns: Vec<Spanned<SpecAlign>>, aligns: SpanVec<SpecAlign>,
h: Option<Spanned<SpecAlign>>, h: Option<Spanned<SpecAlign>>,
v: Option<Spanned<SpecAlign>>, v: Option<Spanned<SpecAlign>>,
} }
parse(header, body, state, f) { parse(header, body, state, f) {
AlignFunc { Self {
body: parse_maybe_body(body, state, f), body: parse_maybe_body(body, state, f),
aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(), aligns: header.args.pos.all::<Spanned<SpecAlign>>().collect(),
h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f), h: header.args.key.get::<Spanned<SpecAlign>>("horizontal", f),

View File

@ -1,16 +1,19 @@
//! The standard library. //! 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::func::prelude::*;
use crate::layout::{LayoutContext, Commands};
use crate::syntax::scope::Scope; use crate::syntax::scope::Scope;
macro_rules! lib { ($name:ident) => { mod $name; pub use $name::*; }} /// Create a scope with all standard library functions.
lib!(font);
lib!(layout);
lib!(page);
lib!(spacing);
/// Create a scope with all standard functions.
pub fn std() -> Scope { pub fn std() -> Scope {
let mut std = Scope::new::<ValFunc>(); let mut std = Scope::new::<ValFunc>();
@ -28,7 +31,10 @@ pub fn std() -> Scope {
} }
function! { 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)] #[derive(Debug, Clone, PartialEq)]
pub struct ValFunc { pub struct ValFunc {
body: Option<SyntaxTree>, body: Option<SyntaxTree>,
@ -37,7 +43,7 @@ function! {
parse(header, body, state, f) { parse(header, body, state, f) {
header.args.pos.0.clear(); header.args.pos.0.clear();
header.args.key.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) { 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<SyntaxTree>,
ctx: LayoutContext<'_>,
data: Option<T>,
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![]
}
}

View File

@ -1,5 +1,3 @@
//! Page setup.
use crate::length::{Length, ScaleLength}; use crate::length::{Length, ScaleLength};
use crate::paper::{Paper, PaperClass}; use crate::paper::{Paper, PaperClass};
use super::*; use super::*;
@ -21,7 +19,7 @@ function! {
parse(header, body, state, f) { parse(header, body, state, f) {
expect_no_body(body, f); expect_no_body(body, f);
PageFunc { Self {
paper: header.args.pos.get::<Paper>(), paper: header.args.pos.get::<Paper>(),
width: header.args.key.get::<Length>("width", f), width: header.args.key.get::<Length>("width", f),
height: header.args.key.get::<Length>("height", f), height: header.args.key.get::<Length>("height", f),

View File

@ -1,13 +1,11 @@
//! Spacing.
use crate::length::ScaleLength;
use crate::layout::SpacingKind; use crate::layout::SpacingKind;
use crate::length::ScaleLength;
use super::*; use super::*;
function! { function! {
/// `parbreak`: Ends the current paragraph. /// `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)] #[derive(Debug, Default, Clone, PartialEq)]
pub struct ParBreakFunc; pub struct ParBreakFunc;
@ -25,7 +23,7 @@ function! {
} }
function! { function! {
/// `h` and `v`: Add spacing along an axis. /// `h` and `v`: Add horizontal or vertical spacing.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct SpacingFunc { pub struct SpacingFunc {
spacing: Option<(SpecAxis, ScaleLength)>, spacing: Option<(SpecAxis, ScaleLength)>,
@ -35,7 +33,7 @@ function! {
parse(header, body, state, f, meta) { parse(header, body, state, f, meta) {
expect_no_body(body, f); expect_no_body(body, f);
SpacingFunc { Self {
spacing: header.args.pos.expect::<ScaleLength>(f) spacing: header.args.pos.expect::<ScaleLength>(f)
.map(|s| (meta, s)) .map(|s| (meta, s))
.or_missing(header.name.span, "spacing", f), .or_missing(header.name.span, "spacing", f),

View File

@ -9,7 +9,7 @@ macro_rules! try_or {
($result:expr, $or:expr $(,)?) => { ($result:expr, $or:expr $(,)?) => {
match $result { match $result {
Ok(v) => v, Ok(v) => v,
Err(_) => { $or } Err(_) => $or,
} }
}; };
} }
@ -19,7 +19,7 @@ macro_rules! try_opt_or {
($option:expr, $or:expr $(,)?) => { ($option:expr, $or:expr $(,)?) => {
match $option { match $option {
Some(v) => v, Some(v) => v,
None => { $or } None => $or,
} }
}; };
} }

View File

@ -16,7 +16,7 @@ pub struct Paper {
impl Paper { impl Paper {
/// The paper with the given name. /// The paper with the given name.
pub fn from_name(name: &str) -> Option<Paper> { pub fn from_name(name: &str) -> Option<Self> {
parse_paper(name) parse_paper(name)
} }
@ -39,7 +39,6 @@ pub enum PaperClass {
impl PaperClass { impl PaperClass {
/// The default margins for this page class. /// The default margins for this page class.
pub fn default_margins(self) -> Value4<ScaleLength> { pub fn default_margins(self) -> Value4<ScaleLength> {
use PaperClass::*;
let values = |l, t, r, b| Value4::new( let values = |l, t, r, b| Value4::new(
ScaleLength::Scaled(l), ScaleLength::Scaled(l),
ScaleLength::Scaled(t), ScaleLength::Scaled(t),
@ -48,11 +47,11 @@ impl PaperClass {
); );
match self { match self {
Custom => values(0.1190, 0.0842, 0.1190, 0.0842), Self::Custom => values(0.1190, 0.0842, 0.1190, 0.0842),
Base => values(0.1190, 0.0842, 0.1190, 0.0842), Self::Base => values(0.1190, 0.0842, 0.1190, 0.0842),
US => values(0.1760, 0.1092, 0.1760, 0.0910), Self::US => values(0.1760, 0.1092, 0.1760, 0.0910),
Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294), Self::Newspaper => values(0.0455, 0.0587, 0.0455, 0.0294),
Book => values(0.1200, 0.0852, 0.1500, 0.0965), Self::Book => values(0.1200, 0.0852, 0.1500, 0.0965),
} }
} }
} }

View File

@ -1,7 +1,10 @@
//! Styles for text and pages. //! Styles for text and pages.
use fontdock::{fallback, FallbackTree, FontVariant, FontStyle, FontWeight, FontWidth}; use fontdock::{
use crate::geom::{Size, Margins, Value4}; fallback, FallbackTree, FontStyle, FontVariant, FontWeight, FontWidth,
};
use crate::geom::{Margins, Size, Value4};
use crate::length::{Length, ScaleLength}; use crate::length::{Length, ScaleLength};
use crate::paper::{Paper, PaperClass, PAPER_A4}; use crate::paper::{Paper, PaperClass, PAPER_A4};
@ -62,8 +65,8 @@ impl TextStyle {
} }
impl Default for TextStyle { impl Default for TextStyle {
fn default() -> TextStyle { fn default() -> Self {
TextStyle { Self {
fallback: fallback! { fallback: fallback! {
list: ["sans-serif"], list: ["sans-serif"],
classes: { classes: {
@ -105,8 +108,8 @@ pub struct PageStyle {
impl PageStyle { impl PageStyle {
/// The default page style for the given paper. /// The default page style for the given paper.
pub fn new(paper: Paper) -> PageStyle { pub fn new(paper: Paper) -> Self {
PageStyle { Self {
class: paper.class, class: paper.class,
size: paper.size(), size: paper.size(),
margins: Value4::with_all(None), margins: Value4::with_all(None),
@ -127,7 +130,7 @@ impl PageStyle {
} }
impl Default for PageStyle { impl Default for PageStyle {
fn default() -> PageStyle { fn default() -> Self {
PageStyle::new(PAPER_A4) Self::new(PAPER_A4)
} }
} }

View File

@ -13,32 +13,16 @@ pub type Decorations = SpanVec<Decoration>;
#[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "serialize", derive(Serialize))]
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
pub enum Decoration { pub enum Decoration {
/// A valid function name. /// A valid, successfully resolved function name.
/// ```typst ResolvedFunc,
/// [box] /// An invalid, unresolved function name.
/// ^^^ UnresolvedFunc,
/// ``` /// A key part of a keyword argument.
ValidFuncName,
/// An invalid function name.
/// ```typst
/// [blabla]
/// ^^^^^^
/// ```
InvalidFuncName,
/// A key of a keyword argument.
/// ```typst
/// [box: width=5cm]
/// ^^^^^
/// ```
ArgumentKey, ArgumentKey,
/// A key in an object. /// A key part of a pair in an object.
/// ```typst
/// [box: padding={ left: 1cm, right: 2cm}]
/// ^^^^ ^^^^^
/// ```
ObjectKey, ObjectKey,
/// An italic word. /// Text in italics.
Italic, Italic,
/// A bold word. /// Text in bold.
Bold, Bold,
} }

View File

@ -5,26 +5,26 @@ use std::ops::Deref;
use std::str::FromStr; use std::str::FromStr;
use std::u8; use std::u8;
use crate::Feedback;
use crate::length::Length; use crate::length::Length;
use super::span::{Spanned, SpanVec}; use crate::Feedback;
use super::span::{SpanVec, Spanned};
use super::tokens::is_identifier; use super::tokens::is_identifier;
use super::value::Value; use super::value::Value;
/// An argument or return value. /// An expression.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum Expr { pub enum Expr {
/// An identifier: `ident`. /// An identifier: `ident`.
Ident(Ident), Ident(Ident),
/// A string: `"string"`. /// A string: `"string"`.
Str(String), Str(String),
/// A boolean: `true, false`.
Bool(bool),
/// A number: `1.2, 200%`. /// A number: `1.2, 200%`.
Number(f64), Number(f64),
/// A length: `2cm, 5.2in`. /// A length: `2cm, 5.2in`.
Length(Length), Length(Length),
/// A bool: `true, false`. /// A color value with alpha channel: `#f79143ff`.
Bool(bool),
/// A color value, including the alpha channel: `#f79143ff`.
Color(RgbaColor), Color(RgbaColor),
/// A tuple: `(false, 12cm, "hi")`. /// A tuple: `(false, 12cm, "hi")`.
Tuple(Tuple), Tuple(Tuple),
@ -32,37 +32,38 @@ pub enum Expr {
NamedTuple(NamedTuple), NamedTuple(NamedTuple),
/// An object: `{ fit: false, width: 12pt }`. /// An object: `{ fit: false, width: 12pt }`.
Object(Object), Object(Object),
/// An operator that negates the contained expression. /// An operation that negates the contained expression.
Neg(Box<Spanned<Expr>>), Neg(Box<Spanned<Expr>>),
/// An operator that adds the contained expressions. /// An operation that adds the contained expressions.
Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>), Add(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
/// An operator that subtracts contained expressions. /// An operation that subtracts the contained expressions.
Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>), Sub(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
/// An operator that multiplies the contained expressions. /// An operation that multiplies the contained expressions.
Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>), Mul(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
/// An operator that divides the contained expressions. /// An operation that divides the contained expressions.
Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>), Div(Box<Spanned<Expr>>, Box<Spanned<Expr>>),
} }
impl Expr { 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 { pub fn name(&self) -> &'static str {
use Expr::*; use Expr::*;
match self { match self {
Ident(_) => "identifier", Ident(_) => "identifier",
Str(_) => "string", Str(_) => "string",
Number(_) => "number", Bool(_) => "bool",
Length(_) => "length", Number(_) => "number",
Bool(_) => "bool", Length(_) => "length",
Color(_) => "color", Color(_) => "color",
Tuple(_) => "tuple", Tuple(_) => "tuple",
NamedTuple(_) => "named tuple", NamedTuple(_) => "named tuple",
Object(_) => "object", Object(_) => "object",
Neg(_) => "negation", Neg(_) => "negation",
Add(_, _) => "addition", Add(_, _) => "addition",
Sub(_, _) => "subtraction", Sub(_, _) => "subtraction",
Mul(_, _) => "multiplication", Mul(_, _) => "multiplication",
Div(_, _) => "division", Div(_, _) => "division",
} }
} }
} }
@ -73,9 +74,9 @@ impl Debug for Expr {
match self { match self {
Ident(i) => i.fmt(f), Ident(i) => i.fmt(f),
Str(s) => s.fmt(f), Str(s) => s.fmt(f),
Bool(b) => b.fmt(f),
Number(n) => n.fmt(f), Number(n) => n.fmt(f),
Length(s) => s.fmt(f), Length(s) => s.fmt(f),
Bool(b) => b.fmt(f),
Color(c) => c.fmt(f), Color(c) => c.fmt(f),
Tuple(t) => t.fmt(f), Tuple(t) => t.fmt(f),
NamedTuple(t) => t.fmt(f), NamedTuple(t) => t.fmt(f),
@ -89,22 +90,15 @@ impl Debug for Expr {
} }
} }
/// A unicode identifier. /// An identifier as defined by unicode with a few extra permissible characters.
///
/// # Example
/// ```typst
/// [func: "hi", ident]
/// ^^^^ ^^^^^
/// ```
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Ident(pub String); pub struct Ident(pub String);
impl Ident { impl Ident {
/// Create a new identifier from a string checking that it is a valid /// Create a new identifier from a string checking that it is a valid.
/// unicode identifier. pub fn new(ident: impl AsRef<str> + Into<String>) -> Option<Self> {
pub fn new<S>(ident: S) -> Option<Ident> where S: AsRef<str> + Into<String> {
if is_identifier(ident.as_ref()) { if is_identifier(ident.as_ref()) {
Some(Ident(ident.into())) Some(Self(ident.into()))
} else { } else {
None None
} }
@ -139,36 +133,36 @@ pub struct RgbaColor {
pub b: u8, pub b: u8,
/// Alpha channel. /// Alpha channel.
pub a: u8, pub a: u8,
/// Indicates whether this is a user-provided value or a /// This is true if this value was provided as a fail-over by the parser
/// default value provided as a fail-over by the parser. /// because the user-defined value was invalid. This color may be
/// This color may be overwritten if this property is true. /// overwritten if this property is true.
pub healed: bool, pub healed: bool,
} }
impl RgbaColor { impl RgbaColor {
/// Constructs a new color. /// Constructs a new color.
pub fn new(r: u8, g: u8, b: u8, a: u8) -> RgbaColor { pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
RgbaColor { r, g, b, a, healed: false } Self { r, g, b, a, healed: false }
} }
/// Constructs a new color with the healed property set to true. /// Constructs a new color with the healed property set to true.
pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> RgbaColor { pub fn new_healed(r: u8, g: u8, b: u8, a: u8) -> Self {
RgbaColor { r, g, b, a, healed: true } Self { r, g, b, a, healed: true }
} }
} }
impl FromStr for RgbaColor { impl FromStr for RgbaColor {
type Err = ParseColorError; type Err = ParseColorError;
/// Constructs a new color from a hex string like `7a03c2`. /// Constructs a new color from a hex string like `7a03c2`. Do not specify a
/// Do not specify a leading `#`. /// leading `#`.
fn from_str(hex_str: &str) -> Result<RgbaColor, Self::Err> { fn from_str(hex_str: &str) -> Result<Self, Self::Err> {
if !hex_str.is_ascii() { if !hex_str.is_ascii() {
return Err(ParseColorError); return Err(ParseColorError);
} }
let len = hex_str.len(); let len = hex_str.len();
let long = len == 6 || len == 8; let long = len == 6 || len == 8;
let short = len == 3 || len == 4; let short = len == 3 || len == 4;
let alpha = len == 4 || len == 8; 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 { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if f.alternate() { if f.alternate() {
write!( write!(
f, f, "rgba({:02}, {:02}, {:02}, {:02})",
"rgba({:02}, {:02}, {:02}, {:02})",
self.r, self.g, self.b, self.a, self.r, self.g, self.b, self.a,
)?; )?;
} else { } else {
write!( write!(
f, f, "#{:02x}{:02x}{:02x}{:02x}",
"#{:02x}{:02x}{:02x}{:02x}",
self.r, self.g, self.b, self.a, 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct ParseColorError; pub struct ParseColorError;
@ -226,7 +218,7 @@ impl std::error::Error for ParseColorError {}
impl fmt::Display for ParseColorError { impl fmt::Display for ParseColorError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { 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<Expr>);
impl Tuple { impl Tuple {
/// Create an empty tuple. /// Create an empty tuple.
pub fn new() -> Tuple { pub fn new() -> Self {
Tuple(vec![]) Self(vec![])
} }
/// Add an element. /// Add an element.
@ -278,13 +270,13 @@ impl Tuple {
let mut i = 0; let mut i = 0;
std::iter::from_fn(move || { std::iter::from_fn(move || {
while i < self.0.len() { while i < self.0.len() {
let val = V::parse(self.0[i].clone(), &mut Feedback::new()); let val = V::parse(self.0[i].clone(), &mut Feedback::new());
if val.is_some() { if val.is_some() {
self.0.remove(i); self.0.remove(i);
return val; return val;
} else { } else {
i += 1; i += 1;
} }
} }
None None
}) })
@ -305,16 +297,16 @@ impl Debug for Tuple {
/// ``` /// ```
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct NamedTuple { pub struct NamedTuple {
/// The name of the tuple and where it is in the user source. /// The name of the tuple.
pub name: Spanned<Ident>, pub name: Spanned<Ident>,
/// The elements of the tuple. /// The elements of the tuple.
pub tuple: Spanned<Tuple>, pub tuple: Spanned<Tuple>,
} }
impl NamedTuple { impl NamedTuple {
/// Create a named tuple from a tuple. /// Create a named tuple from a name and a tuple.
pub fn new(name: Spanned<Ident>, tuple: Spanned<Tuple>) -> NamedTuple { pub fn new(name: Spanned<Ident>, tuple: Spanned<Tuple>) -> Self {
NamedTuple { name, tuple } Self { name, tuple }
} }
} }
@ -338,24 +330,14 @@ pub struct Object(pub SpanVec<Pair>);
/// A key-value pair in an object. /// A key-value pair in an object.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct Pair { pub struct Pair {
/// The key part.
/// ```typst
/// key: value
/// ^^^
/// ```
pub key: Spanned<Ident>, pub key: Spanned<Ident>,
/// The value part.
/// ```typst
/// key: value
/// ^^^^^
/// ```
pub value: Spanned<Expr>, pub value: Spanned<Expr>,
} }
impl Object { impl Object {
/// Create an empty object. /// Create an empty object.
pub fn new() -> Object { pub fn new() -> Self {
Object(vec![]) Self(vec![])
} }
/// Add a pair to object. /// Add a pair to object.
@ -384,13 +366,13 @@ impl Object {
let mut i = 0; let mut i = 0;
std::iter::from_fn(move || { std::iter::from_fn(move || {
while i < self.0.len() { while i < self.0.len() {
let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new()); let val = V::parse(self.0[i].v.value.clone(), &mut Feedback::new());
if let Some(val) = val { if let Some(val) = val {
let pair = self.0.remove(i); let pair = self.0.remove(i);
return Some((pair.v.key, val)); return Some((pair.v.key, val));
} else { } else {
i += 1; i += 1;
} }
} }
None None
}) })

View File

@ -4,19 +4,19 @@
#[macro_use] #[macro_use]
mod test; 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. /// Basic types used around the syntax side.
pub mod prelude { pub mod prelude {
pub use super::expr::*; pub use super::expr::*;
pub use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; pub use super::span::{Span, SpanVec, Spanned};
pub use super::span::{SpanVec, Span, Spanned}; pub use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
pub use super::value::*; 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;

View File

@ -2,36 +2,34 @@
use std::str::FromStr; use std::str::FromStr;
use crate::{Pass, Feedback}; use crate::{Feedback, Pass};
use super::decoration::Decoration; use super::decoration::Decoration;
use super::expr::*; use super::expr::*;
use super::scope::Scope; use super::scope::Scope;
use super::span::{Pos, Span, Spanned}; use super::span::{Pos, Span, Spanned};
use super::tokens::{is_newline_char, Token, Tokens, TokenMode}; use super::tokens::{is_newline_char, Token, TokenMode, Tokens};
use super::tree::{SyntaxTree, SyntaxNode, DynamicNode}; 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<Box<dyn DynamicNode>>; pub type CallParser = dyn Fn(FuncCall, &ParseState) -> Pass<Box<dyn DynamicNode>>;
/// Parse a function call. /// Parse a function call.
pub trait ParseCall { pub trait ParseCall {
/// A metadata type whose value is passed into the function parser. This /// Metadata whose value is passed to `parse`. This allows a single function
/// allows a single function to do different things depending on the value /// to do different things depending on the value that needs to be given
/// that needs to be given when inserting the function into a /// when inserting the function into a scope.
/// [scope](crate::syntax::Scope).
/// ///
/// For example, the functions `word.spacing`, `line.spacing` and /// For example, the functions `h` and `v` are built on the same type.
/// `par.spacing` are actually all the same function
/// [`ContentSpacingFunc`](crate::library::ContentSpacingFunc) with the
/// metadata specifiy which content should be spaced.
type Meta: Clone; type Meta: Clone;
/// Parse the header and body into this function given a context. /// Parse the function call.
fn parse( fn parse(
header: FuncCall, call: FuncCall,
state: &ParseState, state: &ParseState,
metadata: Self::Meta, metadata: Self::Meta,
) -> Pass<Self> where Self: Sized; ) -> Pass<Self>
where
Self: Sized;
} }
/// An invocation of a function. /// An invocation of a function.
@ -58,8 +56,8 @@ pub struct FuncArgs {
impl FuncArgs { impl FuncArgs {
/// Create new empty function arguments. /// Create new empty function arguments.
pub fn new() -> FuncArgs { pub fn new() -> Self {
FuncArgs { Self {
pos: Tuple::new(), pos: Tuple::new(),
key: Object::new(), key: Object::new(),
} }
@ -77,9 +75,7 @@ impl FuncArgs {
/// Either a positional or keyword argument. /// Either a positional or keyword argument.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum FuncArg { pub enum FuncArg {
/// A positional argument.
Pos(Expr), Pos(Expr),
/// A keyword argument.
Key(Pair), Key(Pair),
} }
@ -116,26 +112,21 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
Token::Function { header, body, terminated } => { Token::Function { header, body, terminated } => {
let parsed = FuncParser::new(header, body, state).parse(); let parsed = FuncParser::new(header, body, state).parse();
feedback.extend_offset(parsed.feedback, span.start); feedback.extend_offset(parsed.feedback, span.start);
if !terminated { if !terminated {
error!(@feedback, Span::at(span.end), "expected closing bracket"); error!(@feedback, Span::at(span.end), "expected closing bracket");
} }
parsed.output parsed.output
} }
Token::Star => SyntaxNode::ToggleBolder, Token::Star => SyntaxNode::ToggleBolder,
Token::Underscore => SyntaxNode::ToggleItalic, Token::Underscore => SyntaxNode::ToggleItalic,
Token::Backslash => SyntaxNode::Linebreak, Token::Backslash => SyntaxNode::Linebreak,
Token::Raw { raw, terminated } => { Token::Raw { raw, terminated } => {
if !terminated { if !terminated {
error!(@feedback, Span::at(span.end), "expected backtick"); error!(@feedback, Span::at(span.end), "expected backtick");
} }
SyntaxNode::Raw(unescape_raw(raw)) SyntaxNode::Raw(unescape_raw(raw))
} }
Token::Text(text) => SyntaxNode::Text(text.to_string()), Token::Text(text) => SyntaxNode::Text(text.to_string()),
Token::LineComment(_) | Token::BlockComment(_) => continue, Token::LineComment(_) | Token::BlockComment(_) => continue,
@ -153,17 +144,9 @@ pub fn parse(src: &str, offset: Pos, state: &ParseState) -> Pass<SyntaxTree> {
struct FuncParser<'s> { struct FuncParser<'s> {
state: &'s ParseState, state: &'s ParseState,
/// ```typst /// The tokens inside the header.
/// [tokens][body]
/// ^^^^^^
/// ```
tokens: Tokens<'s>, tokens: Tokens<'s>,
peeked: Option<Option<Spanned<Token<'s>>>>, peeked: Option<Option<Spanned<Token<'s>>>>,
/// The spanned body string if there is a body.
/// ```typst
/// [tokens][body]
/// ^^^^
/// ```
body: Option<Spanned<&'s str>>, body: Option<Spanned<&'s str>>,
feedback: Feedback, feedback: Feedback,
} }
@ -173,8 +156,8 @@ impl<'s> FuncParser<'s> {
header: &'s str, header: &'s str,
body: Option<Spanned<&'s str>>, body: Option<Spanned<&'s str>>,
state: &'s ParseState, state: &'s ParseState,
) -> FuncParser<'s> { ) -> Self {
FuncParser { Self {
state, state,
// Start at column 1 because the opening bracket is also part of // Start at column 1 because the opening bracket is also part of
// the function, but not part of the `header` string. // 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 name = header.name.v.as_str();
let (parser, deco) = match self.state.scope.get_parser(name) { let (parser, deco) = match self.state.scope.get_parser(name) {
// The function exists in the scope. // 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 // The function does not exist in the scope. The parser that is
// returned here is a fallback parser which exists to make sure // returned here is a fallback parser which exists to make sure
@ -199,7 +182,7 @@ impl<'s> FuncParser<'s> {
None => { None => {
error!(@self.feedback, header.name.span, "unknown function"); error!(@self.feedback, header.name.span, "unknown function");
let parser = self.state.scope.get_fallback_parser(); 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(); self.skip_white();
let key = ident; let key = ident;
self.feedback.decorations.push( self.feedback.decorations
Spanned::new(Decoration::ArgumentKey, key.span) .push(Spanned::new(Decoration::ArgumentKey, key.span));
);
let value = try_opt_or!(self.parse_expr(), { let value = try_opt_or!(self.parse_expr(), {
self.expected("value"); self.expected("value");
@ -399,9 +381,9 @@ impl FuncParser<'_> {
self.eat_span(Expr::Str(unescape_string(string))) 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::ExprNumber(n) => self.eat_span(Expr::Number(n)),
Token::ExprLength(s) => self.eat_span(Expr::Length(s)), Token::ExprLength(s) => self.eat_span(Expr::Length(s)),
Token::ExprBool(b) => self.eat_span(Expr::Bool(b)),
Token::ExprHex(s) => { Token::ExprHex(s) => {
if let Ok(color) = RgbaColor::from_str(s) { if let Ok(color) = RgbaColor::from_str(s) {
self.eat_span(Expr::Color(color)) self.eat_span(Expr::Color(color))
@ -411,7 +393,7 @@ impl FuncParser<'_> {
let healed = RgbaColor::new_healed(0, 0, 0, 255); let healed = RgbaColor::new_healed(0, 0, 0, 255);
self.eat_span(Expr::Color(healed)) self.eat_span(Expr::Color(healed))
} }
}, }
// This could be a tuple or a parenthesized expression. We parse as // 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 // a tuple in any case and coerce the tuple into a value if it is
@ -500,9 +482,8 @@ impl FuncParser<'_> {
continue; continue;
} }
self.feedback.decorations.push( self.feedback.decorations
Spanned::new(Decoration::ObjectKey, key.span) .push(Spanned::new(Decoration::ObjectKey, key.span));
);
self.skip_white(); self.skip_white();
let value = try_opt_or!(self.parse_expr(), { let value = try_opt_or!(self.parse_expr(), {
@ -622,7 +603,8 @@ impl<'s> FuncParser<'s> {
} }
fn pos(&self) -> Pos { fn pos(&self) -> Pos {
self.peeked.flatten() self.peeked
.flatten()
.map(|s| s.span.start) .map(|s| s.span.start)
.unwrap_or_else(|| self.tokens.pos()) .unwrap_or_else(|| self.tokens.pos())
} }
@ -687,10 +669,10 @@ mod tests {
use super::*; use super::*;
use Decoration::*; use Decoration::*;
use Expr::{Number as Num, Length as Len, Bool}; use Expr::{Bool, Length as Len, Number as Num};
use SyntaxNode::{ use SyntaxNode::{
Space as S, ToggleItalic as Italic, ToggleBolder as Bold, Space as S, Parbreak, Linebreak, ToggleItalic as Italic,
Parbreak, Linebreak, ToggleBolder as Bold,
}; };
/// Test whether the given string parses into /// Test whether the given string parses into
@ -882,7 +864,7 @@ mod tests {
p!("🌎\n*/[n]" => p!("🌎\n*/[n]" =>
[(0:0, 0:1, T("🌎")), (0:1, 1:0, S), (1:2, 1:5, func!((0:1, 0:2, "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: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]" => p!("[hi]" =>
[func!("hi")], [func!("hi")],
[(0:1, 0:3, "unknown function")], [(0:1, 0:3, "unknown function")],
[(0:1, 0:3, InvalidFuncName)], [(0:1, 0:3, UnresolvedFunc)],
); );
// A valid name. // A valid name.
p!("[f]" => [func!("f")], [], [(0:1, 0:2, ValidFuncName)]); p!("[f]" => [func!("f")], [], [(0:1, 0:2, ResolvedFunc)]);
p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ValidFuncName)]); p!("[ f]" => [func!("f")], [], [(0:3, 0:4, ResolvedFunc)]);
// An invalid token for a name. // An invalid token for a name.
p!("[12]" => [func!("")], [(0:1, 0:3, "expected function name, found number")], []); p!("[12]" => [func!("")], [(0:1, 0:3, "expected function name, found number")], []);
@ -923,7 +905,7 @@ mod tests {
// Valid. // Valid.
p!("[val: true]" => p!("[val: true]" =>
[func!["val": (Bool(true))]], [], [func!["val": (Bool(true))]], [],
[(0:1, 0:4, ValidFuncName)], [(0:1, 0:4, ResolvedFunc)],
); );
// No colon before arg. // No colon before arg.
@ -936,7 +918,7 @@ mod tests {
p!("[val/🌎:$]" => p!("[val/🌎:$]" =>
[func!("val")], [func!("val")],
[(0:4, 0:4, "expected colon")], [(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 // String in invalid header without colon still parsed as string
@ -1159,20 +1141,20 @@ mod tests {
// Correct // Correct
p!("[val: x=true]" => p!("[val: x=true]" =>
[func!("val": (), { "x" => Bool(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 // Spacing around keyword arguments
p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" => p!("\n [val: \n hi \n = /* //\n */ \"s\n\"]" =>
[S, func!("val": (), { "hi" => Str("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 // Missing value
p!("[val: x=]" => p!("[val: x=]" =>
[func!("val")], [func!("val")],
[(0:8, 0:8, "expected value")], [(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() { fn parse_multiple_mixed_arguments() {
p!("[val: 12pt, key=value]" => p!("[val: 12pt, key=value]" =>
[func!("val": (Len(Length::pt(12.0))), { "key" => Id("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") }); pval!("a , x=\"b\" , c" => (Id("a"), Id("c")), { "x" => Str("b") });
} }
@ -1197,7 +1179,7 @@ mod tests {
p!("[val: [hi]]" => p!("[val: [hi]]" =>
[func!("val")], [func!("val")],
[(0:6, 0:10, "expected argument, found function")], [(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")), {})], [func!("val": (Bool(true), Id("you")), {})],
[(0:10, 0:10, "expected comma"), [(0:10, 0:10, "expected comma"),
(0:10, 0:11, "expected argument, found equals sign")], (0:10, 0:11, "expected argument, found equals sign")],
[(0:1, 0:4, ValidFuncName)], [(0:1, 0:4, ResolvedFunc)],
); );
// Unexpected equals. // Unexpected equals.
@ -1223,14 +1205,14 @@ mod tests {
[func!("val": (Id("key"), Num(12.0)), {})], [func!("val": (Id("key"), Num(12.0)), {})],
[(0:9, 0:9, "expected comma"), [(0:9, 0:9, "expected comma"),
(0:9, 0:10, "expected argument, found colon")], (0:9, 0:10, "expected argument, found colon")],
[(0:1, 0:4, ValidFuncName)], [(0:1, 0:4, ResolvedFunc)],
); );
// Invalid colon after unkeyable positional argument. // Invalid colon after unkeyable positional argument.
p!("[val: true:12]" => [func!("val": (Bool(true), Num(12.0)), {})], p!("[val: true:12]" => [func!("val": (Bool(true), Num(12.0)), {})],
[(0:10, 0:10, "expected comma"), [(0:10, 0:10, "expected comma"),
(0:10, 0:11, "expected argument, found colon")], (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 // Space before function
p!(" [val]" => p!(" [val]" =>
[(0:0, 0:1, S), (0:1, 0:6, func!((0:1, 0:4, "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 // Newline before function
p!(" \n\r\n[val]" => p!(" \n\r\n[val]" =>
[(0:0, 2:0, Parbreak), (2:0, 2:5, func!((0:1, 0:4, "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 // Content before function
@ -1293,7 +1275,7 @@ mod tests {
(0:19, 0:20, T("🌎")) (0:19, 0:20, T("🌎"))
], ],
[], [],
[(0:7, 0:10, ValidFuncName)], [(0:7, 0:10, ResolvedFunc)],
); );
// Nested function // 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)],
); );
} }
} }

View File

@ -1,4 +1,4 @@
//! Scopes containing function parsers. //! Mapping of function names to function parsers.
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
@ -15,33 +15,31 @@ pub struct Scope {
impl Scope { impl Scope {
/// Create a new empty scope with a fallback parser that is invoked when no /// Create a new empty scope with a fallback parser that is invoked when no
/// match is found. /// match is found.
pub fn new<F>() -> Scope pub fn new<F>() -> Self
where F: ParseCall<Meta=()> + DynamicNode + 'static { where
Scope { F: ParseCall<Meta = ()> + DynamicNode + 'static
{
Self {
parsers: HashMap::new(), parsers: HashMap::new(),
fallback: make_parser::<F>(()), fallback: make_parser::<F>(()),
} }
} }
/// Create a new scope with the standard functions contained. /// Associate the given function name with a dynamic node type.
pub fn with_std() -> Scope {
crate::library::std()
}
/// Associate the given name with a type that is parseable into a function.
pub fn add<F>(&mut self, name: &str) pub fn add<F>(&mut self, name: &str)
where F: ParseCall<Meta=()> + DynamicNode + 'static { where
F: ParseCall<Meta = ()> + DynamicNode + 'static
{
self.add_with_meta::<F>(name, ()); self.add_with_meta::<F>(name, ());
} }
/// Add a parseable type with additional metadata that is given to the /// Add a dynamic node type with additional metadata that is passed to the
/// parser (other than the default of `()`). /// parser.
pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta) pub fn add_with_meta<F>(&mut self, name: &str, metadata: <F as ParseCall>::Meta)
where F: ParseCall + DynamicNode + 'static { where
self.parsers.insert( F: ParseCall + DynamicNode + 'static
name.to_string(), {
make_parser::<F>(metadata), self.parsers.insert(name.to_string(), make_parser::<F>(metadata));
);
} }
/// Return the parser with the given name if there is one. /// Return the parser with the given name if there is one.
@ -57,14 +55,14 @@ impl Scope {
impl Debug for Scope { impl Debug for Scope {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_set() f.debug_set().entries(self.parsers.keys()).finish()
.entries(self.parsers.keys())
.finish()
} }
} }
fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser> fn make_parser<F>(metadata: <F as ParseCall>::Meta) -> Box<CallParser>
where F: ParseCall + DynamicNode + 'static { where
F: ParseCall + DynamicNode + 'static,
{
Box::new(move |f, s| { Box::new(move |f, s| {
F::parse(f, s, metadata.clone()) F::parse(f, s, metadata.clone())
.map(|tree| Box::new(tree) as Box<dyn DynamicNode>) .map(|tree| Box::new(tree) as Box<dyn DynamicNode>)

View File

@ -36,13 +36,13 @@ pub struct Spanned<T> {
impl<T> Spanned<T> { impl<T> Spanned<T> {
/// Create a new instance from a value and its span. /// Create a new instance from a value and its span.
pub fn new(v: T, span: Span) -> Spanned<T> { pub fn new(v: T, span: Span) -> Self {
Spanned { v, span } Self { v, span }
} }
/// Create a new instance from a value with the zero span. /// Create a new instance from a value with the zero span.
pub fn zero(v: T) -> Spanned<T> { pub fn zero(v: T) -> Self {
Spanned { v, span: Span::ZERO } Self { v, span: Span::ZERO }
} }
/// Access the value. /// Access the value.
@ -51,12 +51,12 @@ impl<T> Spanned<T> {
} }
/// Map the value using a function while keeping the span. /// Map the value using a function while keeping the span.
pub fn map<V, F>(self, f: F) -> Spanned<V> where F: FnOnce(T) -> V { pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Spanned<U> {
Spanned { v: f(self.v), span: self.span } Spanned { v: f(self.v), span: self.span }
} }
/// Maps the span while keeping the value. /// Maps the span while keeping the value.
pub fn map_span<F>(mut self, f: F) -> Spanned<T> where F: FnOnce(Span) -> Span { pub fn map_span(mut self, f: impl FnOnce(Span) -> Span) -> Self {
self.span = f(self.span); self.span = f(self.span);
self self
} }
@ -91,35 +91,35 @@ pub struct Span {
impl Span { impl Span {
/// The zero 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. /// Create a new span from start and end positions.
pub fn new(start: Pos, end: Pos) -> Span { pub fn new(start: Pos, end: Pos) -> Self {
Span { start, end } Self { start, end }
} }
/// Create a span including just a single position. /// Create a span including just a single position.
pub fn at(pos: Pos) -> Span { pub fn at(pos: Pos) -> Self {
Span { start: pos, end: pos } Self { start: pos, end: pos }
} }
/// Create a new span with the earlier start and later end position. /// Create a new span with the earlier start and later end position.
pub fn merge(a: Span, b: Span) -> Span { pub fn merge(a: Self, b: Self) -> Self {
Span { Self {
start: a.start.min(b.start), start: a.start.min(b.start),
end: a.end.max(b.end), end: a.end.max(b.end),
} }
} }
/// Expand a span by merging it with another span. /// Expand a span by merging it with another span.
pub fn expand(&mut self, other: Span) { pub fn expand(&mut self, other: Self) {
*self = Span::merge(*self, other) *self = Self::merge(*self, other)
} }
} }
impl Offset for Span { impl Offset for Span {
fn offset(self, by: Pos) -> Self { fn offset(self, by: Pos) -> Self {
Span { Self {
start: self.start.offset(by), start: self.start.offset(by),
end: self.end.offset(by), end: self.end.offset(by),
} }
@ -144,31 +144,31 @@ pub struct Pos {
impl Pos { impl Pos {
/// The line 0, column 0 position. /// 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. /// Create a new position from line and column.
pub fn new(line: usize, column: usize) -> Pos { pub fn new(line: usize, column: usize) -> Self {
Pos { line, column } Self { line, column }
} }
} }
impl Offset for Pos { impl Offset for Pos {
fn offset(self, by: Pos) -> Self { fn offset(self, by: Self) -> Self {
by + self by + self
} }
} }
impl Add for Pos { 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 { if rhs.line == 0 {
Pos { Self {
line: self.line, line: self.line,
column: self.column + rhs.column column: self.column + rhs.column,
} }
} else { } else {
Pos { Self {
line: self.line + rhs.line, line: self.line + rhs.line,
column: rhs.column, column: rhs.column,
} }
@ -177,16 +177,16 @@ impl Add for Pos {
} }
impl Sub 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 { if self.line == rhs.line {
Pos { Self {
line: 0, line: 0,
column: self.column - rhs.column column: self.column - rhs.column,
} }
} else { } else {
Pos { Self {
line: self.line - rhs.line, line: self.line - rhs.line,
column: self.column, column: self.column,
} }

View File

@ -2,15 +2,16 @@ use std::fmt::Debug;
use crate::func::parse_maybe_body; use crate::func::parse_maybe_body;
use super::decoration::Decoration; use super::decoration::Decoration;
use super::expr::{Expr, Ident, Tuple, NamedTuple, Object, Pair}; use super::expr::{Expr, Ident, NamedTuple, Object, Pair, Tuple};
use super::parsing::{FuncHeader, FuncArgs, FuncArg}; use super::parsing::{FuncArg, FuncArgs, FuncHeader};
use super::span::Spanned; use super::span::Spanned;
use super::tokens::Token; 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<T>(src: &str, exp: T, found: T, cmp_spans: bool) pub fn check<T>(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 }; let cmp = if cmp_spans { PartialEq::eq } else { SpanlessEq::spanless_eq };
if !cmp(&exp, &found) { if !cmp(&exp, &found) {
println!("source: {:?}", src); println!("source: {:?}", src);
@ -41,7 +42,7 @@ macro_rules! span_vec {
} }
macro_rules! span_item { 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}; use $crate::syntax::span::{Pos, Span, Spanned};
Spanned { Spanned {
span: Span::new( span: Span::new(
@ -50,7 +51,7 @@ macro_rules! span_item {
), ),
v: $v v: $v
} }
}); }};
($v:expr) => { ($v:expr) => {
$crate::syntax::span::Spanned::zero($v) $crate::syntax::span::Spanned::zero($v)
@ -70,7 +71,7 @@ function! {
let cloned = header.clone(); let cloned = header.clone();
header.args.pos.0.clear(); header.args.pos.0.clear();
header.args.key.0.clear(); header.args.key.0.clear();
DebugFn { Self {
header: cloned, header: cloned,
body: parse_maybe_body(body, state, f), body: parse_maybe_body(body, state, f),
} }
@ -80,7 +81,7 @@ function! {
} }
/// Compares elements by only looking at values and ignoring spans. /// Compares elements by only looking at values and ignoring spans.
pub trait SpanlessEq<Rhs=Self> { pub trait SpanlessEq<Rhs = Self> {
fn spanless_eq(&self, other: &Rhs) -> bool; fn spanless_eq(&self, other: &Rhs) -> bool;
} }
@ -102,20 +103,21 @@ impl SpanlessEq for SyntaxNode {
impl SpanlessEq for DebugFn { impl SpanlessEq for DebugFn {
fn spanless_eq(&self, other: &DebugFn) -> bool { fn spanless_eq(&self, other: &DebugFn) -> bool {
self.header.spanless_eq(&other.header) self.header.spanless_eq(&other.header)
&& self.body.spanless_eq(&other.body) && self.body.spanless_eq(&other.body)
} }
} }
impl SpanlessEq for FuncHeader { impl SpanlessEq for FuncHeader {
fn spanless_eq(&self, other: &Self) -> bool { 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 { impl SpanlessEq for FuncArgs {
fn spanless_eq(&self, other: &Self) -> bool { fn spanless_eq(&self, other: &Self) -> bool {
self.key.spanless_eq(&other.key) 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 { impl SpanlessEq for NamedTuple {
fn spanless_eq(&self, other: &NamedTuple) -> bool { fn spanless_eq(&self, other: &NamedTuple) -> bool {
self.name.v == other.name.v 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<T: SpanlessEq> SpanlessEq for Vec<T> { impl<T: SpanlessEq> SpanlessEq for Vec<T> {
fn spanless_eq(&self, other: &Vec<T>) -> bool { fn spanless_eq(&self, other: &Vec<T>) -> bool {
self.len() == other.len() 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))
} }
} }

View File

@ -9,7 +9,6 @@ use super::span::{Pos, Span, Spanned};
use Token::*; use Token::*;
use TokenMode::*; use TokenMode::*;
/// A minimal semantic entity of source code. /// A minimal semantic entity of source code.
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum Token<'s> { pub enum Token<'s> {
@ -71,14 +70,14 @@ pub enum Token<'s> {
/// a String. The escaping is done later in the parser. /// a String. The escaping is done later in the parser.
string: &'s str, string: &'s str,
/// Whether the closing quote was present. /// 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`. /// A number in a function header: `3.14`.
ExprNumber(f64), ExprNumber(f64),
/// A length in a function header: `12pt`. /// A length in a function header: `12pt`.
ExprLength(Length), ExprLength(Length),
/// A boolean in a function header: `true | false`.
ExprBool(bool),
/// A hex value in a function header: `#20d82a`. /// A hex value in a function header: `#20d82a`.
ExprHex(&'s str), ExprHex(&'s str),
/// A plus in a function header, signifying the addition of expressions. /// A plus in a function header, signifying the addition of expressions.
@ -130,9 +129,9 @@ impl<'s> Token<'s> {
Equals => "equals sign", Equals => "equals sign",
ExprIdent(_) => "identifier", ExprIdent(_) => "identifier",
ExprStr { .. } => "string", ExprStr { .. } => "string",
ExprBool(_) => "bool",
ExprNumber(_) => "number", ExprNumber(_) => "number",
ExprLength(_) => "length", ExprLength(_) => "length",
ExprBool(_) => "bool",
ExprHex(_) => "hex value", ExprHex(_) => "hex value",
Plus => "plus", Plus => "plus",
Hyphen => "minus", Hyphen => "minus",
@ -173,8 +172,8 @@ impl<'s> Tokens<'s> {
/// ///
/// The first token's span starts an the given `offset` position instead of /// The first token's span starts an the given `offset` position instead of
/// the zero position. /// the zero position.
pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Tokens<'s> { pub fn new(src: &'s str, offset: Pos, mode: TokenMode) -> Self {
Tokens { Self {
src, src,
mode, mode,
iter: src.chars().peekable(), iter: src.chars().peekable(),
@ -200,7 +199,7 @@ impl<'s> Iterator for Tokens<'s> {
type Item = Spanned<Token<'s>>; type Item = Spanned<Token<'s>>;
/// Parse the next token in the source code. /// Parse the next token in the source code.
fn next(&mut self) -> Option<Spanned<Token<'s>>> { fn next(&mut self) -> Option<Self::Item> {
let start = self.pos(); let start = self.pos();
let first = self.eat()?; let first = self.eat()?;
@ -366,7 +365,7 @@ impl<'s> Tokens<'s> {
} }
let end = self.index(); let end = self.index();
(&self.src[start .. end], terminated) (&self.src[start..end], terminated)
} }
fn read_string(&mut self) -> Token<'s> { fn read_string(&mut self) -> Token<'s> {
@ -404,7 +403,7 @@ impl<'s> Tokens<'s> {
Some(c) if is_escapable(c) => { Some(c) if is_escapable(c) => {
let index = self.index(); let index = self.index();
self.eat(); 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(c) if c.is_whitespace() => Backslash,
Some(_) => Text("\\"), Some(_) => Text("\\"),
@ -442,13 +441,13 @@ impl<'s> Tokens<'s> {
/// Returns the string from the index where this was called offset by /// 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 /// `offset_start` to the end offset by `offset_end`. The end is before or
/// after the match depending on `eat_match`. /// after the match depending on `eat_match`.
fn read_string_until<F>( fn read_string_until(
&mut self, &mut self,
mut f: F, mut f: impl FnMut(char) -> bool,
eat_match: bool, eat_match: bool,
offset_start: isize, offset_start: isize,
offset_end: 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 start = ((self.index() as isize) + offset_start) as usize;
let mut matched = false; let mut matched = false;
@ -469,7 +468,7 @@ impl<'s> Tokens<'s> {
end = ((end as isize) + offset_end) as usize; end = ((end as isize) + offset_end) as usize;
} }
(&self.src[start .. end], matched) (&self.src[start..end], matched)
} }
fn eat(&mut self) -> Option<char> { fn eat(&mut self) -> Option<char> {
@ -493,7 +492,7 @@ impl<'s> Tokens<'s> {
fn parse_percentage(text: &str) -> Option<f64> { fn parse_percentage(text: &str) -> Option<f64> {
if text.ends_with('%') { if text.ends_with('%') {
text[.. text.len() - 1].parse::<f64>().ok() text[..text.len() - 1].parse::<f64>().ok()
} else { } else {
None None
} }
@ -503,7 +502,7 @@ fn parse_percentage(text: &str) -> Option<f64> {
pub fn is_newline_char(character: char) -> bool { pub fn is_newline_char(character: char) -> bool {
match character { match character {
// Line Feed, Vertical Tab, Form Feed, Carriage Return. // Line Feed, Vertical Tab, Form Feed, Carriage Return.
'\x0A' ..= '\x0D' => true, '\x0A'..='\x0D' => true,
// Next Line, Line Separator, Paragraph Separator. // Next Line, Line Separator, Paragraph Separator.
'\u{0085}' | '\u{2028}' | '\u{2029}' => true, '\u{0085}' | '\u{2028}' | '\u{2029}' => true,
_ => false, _ => false,
@ -544,15 +543,15 @@ mod tests {
LeftParen as LP, RightParen as RP, LeftParen as LP, RightParen as RP,
LeftBrace as LB, RightBrace as RB, LeftBrace as LB, RightBrace as RB,
ExprIdent as Id, ExprIdent as Id,
ExprBool as Bool,
ExprNumber as Num, ExprNumber as Num,
ExprLength as Len, ExprLength as Len,
ExprBool as Bool,
ExprHex as Hex, ExprHex as Hex,
Text as T,
Plus, Plus,
Hyphen as Min, Hyphen as Min,
Star,
Slash, Slash,
Star,
Text as T,
}; };
/// Test whether the given string tokenizes into the given list of tokens. /// Test whether the given string tokenizes into the given list of tokens.

View File

@ -6,7 +6,7 @@ use std::fmt::Debug;
use crate::layout::Layout; use crate::layout::Layout;
use super::span::SpanVec; 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<SyntaxNode>; pub type SyntaxTree = SpanVec<SyntaxNode>;
/// A syntax node, which encompasses a single logical entity of parsed source /// A syntax node, which encompasses a single logical entity of parsed source
@ -27,7 +27,7 @@ pub enum SyntaxNode {
ToggleItalic, ToggleItalic,
/// Bolder was enabled / disabled. /// Bolder was enabled / disabled.
ToggleBolder, ToggleBolder,
/// A subtree, typically a function invocation. /// A dynamic node, create through function invocations in source code.
Dyn(Box<dyn DynamicNode>), Dyn(Box<dyn DynamicNode>),
} }
@ -65,13 +65,16 @@ pub trait DynamicNode: Debug + Layout {
impl dyn DynamicNode { impl dyn DynamicNode {
/// Downcast this dynamic node to a concrete node. /// Downcast this dynamic node to a concrete node.
pub fn downcast<N>(&self) -> Option<&N> where N: DynamicNode + 'static { pub fn downcast<T>(&self) -> Option<&T>
self.as_any().downcast_ref::<N>() where
T: DynamicNode + 'static,
{
self.as_any().downcast_ref::<T>()
} }
} }
impl PartialEq for dyn DynamicNode { impl PartialEq for dyn DynamicNode {
fn eq(&self, other: &dyn DynamicNode) -> bool { fn eq(&self, other: &Self) -> bool {
self.dyn_eq(other) self.dyn_eq(other)
} }
} }
@ -82,7 +85,10 @@ impl Clone for Box<dyn DynamicNode> {
} }
} }
impl<T> DynamicNode for T where T: Debug + PartialEq + Clone + Layout + 'static { impl<T> DynamicNode for T
where
T: Debug + PartialEq + Clone + Layout + 'static,
{
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
self self
} }

View File

@ -2,18 +2,20 @@
use fontdock::{FontStyle, FontWeight, FontWidth}; use fontdock::{FontStyle, FontWeight, FontWidth};
use crate::Feedback;
use crate::layout::prelude::*; use crate::layout::prelude::*;
use crate::length::{Length, ScaleLength}; use crate::length::{Length, ScaleLength};
use crate::paper::Paper; use crate::paper::Paper;
use super::span::Spanned; use crate::Feedback;
use super::expr::*; use super::expr::*;
use super::span::Spanned;
/// Value types are used to extract values from functions, tuples and /// Value types are used to extract values from functions, tuples and
/// objects. They represent the value part of an argument. /// objects. They represent the value part of an argument.
///
/// # Example
/// ```typst /// ```typst
/// [func: value, key=value] /// [func: 12pt, key="these are both values"]
/// ^^^^^ ^^^^^ /// ^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
/// ``` /// ```
pub trait Value: Sized { pub trait Value: Sized {
/// Try to parse this value from an expression. /// Try to parse this value from an expression.
@ -53,8 +55,8 @@ macro_rules! match_value {
match_value!(Expr, "expression", e => e); match_value!(Expr, "expression", e => e);
match_value!(Ident, "identifier", Expr::Ident(i) => i); match_value!(Ident, "identifier", Expr::Ident(i) => i);
match_value!(String, "string", Expr::Str(s) => s); 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!(bool, "bool", Expr::Bool(b) => b);
match_value!(f64, "number", Expr::Number(n) => n);
match_value!(Length, "length", Expr::Length(l) => l); match_value!(Length, "length", Expr::Length(l) => l);
match_value!(Tuple, "tuple", Expr::Tuple(t) => t); match_value!(Tuple, "tuple", Expr::Tuple(t) => t);
match_value!(Object, "object", Expr::Object(o) => o); match_value!(Object, "object", Expr::Object(o) => o);
@ -63,7 +65,7 @@ match_value!(ScaleLength, "number or length",
Expr::Number(scale) => ScaleLength::Scaled(scale), 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<String>`. /// `Into<String>`.
pub struct StringLike(pub String); pub struct StringLike(pub String);
@ -101,7 +103,7 @@ macro_rules! ident_value {
} }
ident_value!(Dir, "direction", |s| match s { ident_value!(Dir, "direction", |s| match s {
"ltr" => Some(LTT), "ltr" => Some(LTR),
"rtl" => Some(RTL), "rtl" => Some(RTL),
"ttb" => Some(TTB), "ttb" => Some(TTB),
"btt" => Some(BTT), "btt" => Some(BTT),
@ -109,11 +111,11 @@ ident_value!(Dir, "direction", |s| match s {
}); });
ident_value!(SpecAlign, "alignment", |s| match s { ident_value!(SpecAlign, "alignment", |s| match s {
"left" => Some(SpecAlign::Left), "left" => Some(Self::Left),
"right" => Some(SpecAlign::Right), "right" => Some(Self::Right),
"top" => Some(SpecAlign::Top), "top" => Some(Self::Top),
"bottom" => Some(SpecAlign::Bottom), "bottom" => Some(Self::Bottom),
"center" => Some(SpecAlign::Center), "center" => Some(Self::Center),
_ => None, _ => None,
}); });
@ -127,7 +129,7 @@ impl Value for FontWeight {
const MIN: u16 = 100; const MIN: u16 = 100;
const MAX: u16 = 900; 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); error!(@f, expr.span, "the minimum font weight is {}", MIN);
MIN MIN
} else if weight > MAX as f64 { } else if weight > MAX as f64 {
@ -163,7 +165,7 @@ impl Value for FontWidth {
const MIN: u16 = 1; const MIN: u16 = 1;
const MAX: u16 = 9; 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); error!(@f, expr.span, "the minimum font width is {}", MIN);
MIN MIN
} else if width > MAX as f64 { } else if width > MAX as f64 {

View File

@ -5,21 +5,21 @@ use std::fs::{self, File};
use std::io::BufWriter; use std::io::BufWriter;
use std::rc::Rc; use std::rc::Rc;
use fontdock::fs::{FsIndex, FsProvider};
use fontdock::FontLoader;
use futures_executor::block_on; 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 ttf_parser::OutlineBuilder;
use typstc::Typesetter; use typstc::export::pdf;
use typstc::font::{DynProvider, SharedFontLoader}; use typstc::font::{DynProvider, SharedFontLoader};
use typstc::geom::{Size, Value4}; use typstc::geom::{Size, Value4};
use typstc::layout::MultiLayout;
use typstc::layout::elements::{LayoutElement, Shaped}; use typstc::layout::elements::{LayoutElement, Shaped};
use typstc::layout::MultiLayout;
use typstc::length::Length; use typstc::length::Length;
use typstc::style::PageStyle;
use typstc::paper::PaperClass; use typstc::paper::PaperClass;
use typstc::export::pdf; use typstc::style::PageStyle;
use fontdock::FontLoader; use typstc::Typesetter;
use fontdock::fs::{FsIndex, FsProvider};
const TEST_DIR: &str = "tests"; const TEST_DIR: &str = "tests";
const OUT_DIR: &str = "tests/out"; const OUT_DIR: &str = "tests/out";
@ -38,12 +38,7 @@ fn main() {
continue; continue;
} }
let name = path let name = path.file_stem().unwrap().to_string_lossy().to_string();
.file_stem()
.unwrap()
.to_string_lossy()
.to_string();
if filter.matches(&name) { if filter.matches(&name) {
let src = fs::read_to_string(&path).unwrap(); let src = fs::read_to_string(&path).unwrap();
filtered.push((name, src)); filtered.push((name, src));
@ -93,18 +88,15 @@ fn test(
let typeset = block_on(typesetter.typeset(src)); let typeset = block_on(typesetter.typeset(src));
let layouts = typeset.output; let layouts = typeset.output;
for diagnostic in typeset.feedback.diagnostics { for diagnostic in typeset.feedback.diagnostics {
println!(" {:?} {:?}: {}", println!(
diagnostic.v.level, " {:?} {:?}: {}",
diagnostic.span, diagnostic.v.level, diagnostic.span, diagnostic.v.message,
diagnostic.v.message
); );
} }
// Render the PNG file.
let png_path = format!("{}/{}.png", OUT_DIR, name); let png_path = format!("{}/{}.png", OUT_DIR, name);
render(&layouts, &loader, 3.0).write_png(png_path).unwrap(); render(&layouts, &loader, 3.0).write_png(png_path).unwrap();
// Write the PDF file.
let pdf_path = format!("{}/{}.pdf", OUT_DIR, name); let pdf_path = format!("{}/{}.pdf", OUT_DIR, name);
let file = BufWriter::new(File::create(pdf_path).unwrap()); let file = BufWriter::new(File::create(pdf_path).unwrap());
pdf::export(&layouts, &loader, file).unwrap(); pdf::export(&layouts, &loader, file).unwrap();
@ -116,19 +108,19 @@ struct TestFilter {
} }
impl TestFilter { impl TestFilter {
fn new(args: impl Iterator<Item=String>) -> TestFilter { fn new(args: impl Iterator<Item = String>) -> Self {
let mut filter = Vec::new(); let mut filter = Vec::new();
let mut perfect = false; let mut perfect = false;
for arg in args { for arg in args {
match arg.as_str() { match arg.as_str() {
"--nocapture" => {}, "--nocapture" => {}
"=" => perfect = true, "=" => perfect = true,
_ => filter.push(arg), _ => filter.push(arg),
} }
} }
TestFilter { filter, perfect } Self { filter, perfect }
} }
fn matches(&self, name: &str) -> bool { fn matches(&self, name: &str) -> bool {
@ -136,7 +128,7 @@ impl TestFilter {
self.filter.iter().any(|p| name == p) self.filter.iter().any(|p| name == p)
} else { } else {
self.filter.is_empty() 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 { for &(pos, ref element) in &layout.elements.0 {
match element { match element {
LayoutElement::Text(shaped) => { LayoutElement::Text(shaped) => render_shaped(
render_shaped( &mut surface,
&mut surface, loader,
loader, shaped,
shaped, scale * pos + offset,
scale * pos + offset, scale,
scale, ),
);
},
} }
} }
@ -215,7 +205,7 @@ fn render_shaped(
let t = Transform::create_scale(s as f32, -s as f32) let t = Transform::create_scale(s as f32, -s as f32)
.post_translate(Vector::new(x as f32, y as f32)); .post_translate(Vector::new(x as f32, y as f32));
surface.fill( surface.fill(
&path.transform(&t), &path.transform(&t),
&Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }), &Source::Solid(SolidSource { r: 0, g: 0, b: 0, a: 255 }),
&Default::default(), &Default::default(),