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]
futures-executor = "0.3"
serde_json = "1"
raqote = { version = "0.7", default-features = false }
raqote = { version = "0.8", default-features = false }
[[test]]
name = "test-typeset"

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,19 @@
//! The standard library.
mod font;
mod layout;
mod page;
mod spacing;
pub use font::*;
pub use layout::*;
pub use page::*;
pub use spacing::*;
use crate::func::prelude::*;
use crate::layout::{LayoutContext, Commands};
use crate::syntax::scope::Scope;
macro_rules! lib { ($name:ident) => { mod $name; pub use $name::*; }}
lib!(font);
lib!(layout);
lib!(page);
lib!(spacing);
/// Create a scope with all standard functions.
/// Create a scope with all standard library functions.
pub fn std() -> Scope {
let mut std = Scope::new::<ValFunc>();
@ -28,7 +31,10 @@ pub fn std() -> Scope {
}
function! {
/// `val`: Layouts the body with no special effect.
/// `val`: Ignores all arguments and layouts the body flatly.
///
/// This is also the fallback function, which is used when a function name
/// could not be resolved.
#[derive(Debug, Clone, PartialEq)]
pub struct ValFunc {
body: Option<SyntaxTree>,
@ -37,7 +43,7 @@ function! {
parse(header, body, state, f) {
header.args.pos.0.clear();
header.args.key.0.clear();
ValFunc { body: parse_maybe_body(body, state, f), }
Self { body: parse_maybe_body(body, state, f), }
}
layout(self, ctx, f) {
@ -47,27 +53,3 @@ function! {
}
}
}
/// Layout an optional body with a change of the text style.
fn styled<'a, T, F>(
body: &'a Option<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::paper::{Paper, PaperClass};
use super::*;
@ -21,7 +19,7 @@ function! {
parse(header, body, state, f) {
expect_no_body(body, f);
PageFunc {
Self {
paper: header.args.pos.get::<Paper>(),
width: header.args.key.get::<Length>("width", 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::length::ScaleLength;
use super::*;
function! {
/// `parbreak`: Ends the current paragraph.
///
/// self has the same effect as two subsequent newlines.
/// This has the same effect as two subsequent newlines.
#[derive(Debug, Default, Clone, PartialEq)]
pub struct ParBreakFunc;
@ -25,7 +23,7 @@ function! {
}
function! {
/// `h` and `v`: Add spacing along an axis.
/// `h` and `v`: Add horizontal or vertical spacing.
#[derive(Debug, Clone, PartialEq)]
pub struct SpacingFunc {
spacing: Option<(SpecAxis, ScaleLength)>,
@ -35,7 +33,7 @@ function! {
parse(header, body, state, f, meta) {
expect_no_body(body, f);
SpacingFunc {
Self {
spacing: header.args.pos.expect::<ScaleLength>(f)
.map(|s| (meta, s))
.or_missing(header.name.span, "spacing", f),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,19 +4,19 @@
#[macro_use]
mod test;
pub mod decoration;
pub mod expr;
pub mod parsing;
pub mod scope;
pub mod span;
pub mod tokens;
pub mod tree;
pub mod value;
/// Basic types used around the syntax side.
pub mod prelude {
pub use super::expr::*;
pub use super::tree::{SyntaxTree, SyntaxNode, DynamicNode};
pub use super::span::{SpanVec, Span, Spanned};
pub use super::span::{Span, SpanVec, Spanned};
pub use super::tree::{DynamicNode, SyntaxNode, SyntaxTree};
pub use super::value::*;
}
pub mod decoration;
pub mod expr;
pub mod tree;
pub mod parsing;
pub mod span;
pub mod scope;
pub mod tokens;
pub mod value;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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