Formatting, documentation and small improvements 🧽
This commit is contained in:
parent
5a8f2fb73d
commit
dbfb3d2ced
@ -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"
|
||||
|
43
main/main.rs
43
main/main.rs
@ -1,44 +1,37 @@
|
||||
use std::cell::RefCell;
|
||||
use std::error::Error;
|
||||
use std::fs::{File, read_to_string};
|
||||
use std::fs::{read_to_string, File};
|
||||
use std::io::BufWriter;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
use futures_executor::block_on;
|
||||
|
||||
use fontdock::fs::{FsIndex, FsProvider};
|
||||
use fontdock::FontLoader;
|
||||
use typstc::Typesetter;
|
||||
use typstc::font::DynProvider;
|
||||
use futures_executor::block_on;
|
||||
|
||||
use typstc::export::pdf;
|
||||
use typstc::font::DynProvider;
|
||||
use typstc::Typesetter;
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
eprintln!("error: {}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Box<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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
21
src/font.rs
21
src/font.rs
@ -3,14 +3,15 @@
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
|
||||
use fontdock::{ContainsChar, FaceFromVec, FontLoader, FontProvider};
|
||||
use ttf_parser::Face;
|
||||
use fontdock::{FontLoader, FontProvider, ContainsChar, FaceFromVec};
|
||||
|
||||
/// A referenced-count shared font loader backed by a dynamic provider.
|
||||
pub type SharedFontLoader = Rc<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()
|
||||
|
27
src/func.rs
27
src/func.rs
@ -1,20 +1,20 @@
|
||||
//! Tools for building custom functions.
|
||||
|
||||
use crate::Feedback;
|
||||
use crate::syntax::span::{Span, Spanned};
|
||||
use crate::syntax::parsing::{parse, ParseState};
|
||||
use crate::syntax::tree::SyntaxTree;
|
||||
|
||||
/// Useful things for creating functions.
|
||||
pub mod prelude {
|
||||
pub use crate::layout::prelude::*;
|
||||
pub use crate::layout::Command::{self, *};
|
||||
pub use crate::syntax::prelude::*;
|
||||
pub use crate::style::*;
|
||||
pub use super::{OptionExt, parse_maybe_body, expect_no_body};
|
||||
pub use crate::syntax::prelude::*;
|
||||
pub use super::{expect_no_body, parse_maybe_body, OptionExt};
|
||||
}
|
||||
|
||||
/// Extra methods on [`Options`](Option) used for function argument parsing.
|
||||
use crate::syntax::parsing::{parse, ParseState};
|
||||
use crate::syntax::span::{Span, Spanned};
|
||||
use crate::syntax::tree::SyntaxTree;
|
||||
use crate::Feedback;
|
||||
|
||||
/// Extra methods on `Option`s used for function argument parsing.
|
||||
pub trait OptionExt<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;
|
||||
|
66
src/geom.rs
66
src/geom.rs
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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![],
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
95
src/lib.rs
95
src/lib.rs
@ -2,18 +2,40 @@
|
||||
//!
|
||||
//! # Steps
|
||||
//! - **Parsing:** The parsing step first transforms a plain string into an
|
||||
//! [iterator of tokens](crate::syntax::Tokens). Then, a parser constructs a
|
||||
//! syntax tree from the token stream. The structures describing the tree can
|
||||
//! be found in the [syntax](crate::syntax) module.
|
||||
//! [iterator of tokens][tokens]. Then, a parser constructs a syntax tree from
|
||||
//! the token stream. The structures describing the tree can be found in the
|
||||
//! [syntax] module.
|
||||
//! - **Layouting:** The next step is to transform the syntax tree into a
|
||||
//! portable representation of the typesetted document. Types for these can be
|
||||
//! found in the [layout](crate::layout) module. A finished layout reading for
|
||||
//! exporting is a [MultiLayout](crate::layout::MultiLayout) consisting of
|
||||
//! multiple boxes (or pages).
|
||||
//! found in the [layout] module. A finished layout ready for exporting is a
|
||||
//! [`MultiLayout`] consisting of multiple boxes (or pages).
|
||||
//! - **Exporting:** The finished layout can then be exported into a supported
|
||||
//! format. Submodules for these formats are located in the
|
||||
//! [export](crate::export) module. Currently, the only supported output
|
||||
//! format is [_PDF_](crate::export::pdf).
|
||||
//! format. Submodules for these formats are located in the [export] module.
|
||||
//! Currently, the only supported output format is [_PDF_].
|
||||
//!
|
||||
//! [tokens]: syntax/tokens/struct.Tokens.html
|
||||
//! [syntax]: syntax/index.html
|
||||
//! [layout]: layout/index.html
|
||||
//! [export]: export/index.html
|
||||
//! [_PDF_]: export/pdf/index.html
|
||||
//! [`MultiLayout`]: layout/type.MultiLayout.html
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
#[macro_use]
|
||||
pub mod diagnostic;
|
||||
#[macro_use]
|
||||
pub mod func;
|
||||
|
||||
pub mod export;
|
||||
pub mod font;
|
||||
pub mod geom;
|
||||
pub mod layout;
|
||||
pub mod length;
|
||||
pub mod library;
|
||||
pub mod paper;
|
||||
pub mod style;
|
||||
pub mod syntax;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
@ -24,26 +46,9 @@ use crate::font::SharedFontLoader;
|
||||
use crate::layout::MultiLayout;
|
||||
use crate::style::{LayoutStyle, PageStyle, TextStyle};
|
||||
use crate::syntax::decoration::Decorations;
|
||||
use crate::syntax::tree::SyntaxTree;
|
||||
use crate::syntax::parsing::{parse, ParseState};
|
||||
use crate::syntax::scope::Scope;
|
||||
use crate::syntax::span::{Offset, Pos};
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
#[macro_use]
|
||||
pub mod diagnostic;
|
||||
pub mod export;
|
||||
pub mod font;
|
||||
#[macro_use]
|
||||
pub mod func;
|
||||
pub mod geom;
|
||||
pub mod layout;
|
||||
pub mod library;
|
||||
pub mod length;
|
||||
pub mod paper;
|
||||
pub mod style;
|
||||
pub mod syntax;
|
||||
use crate::syntax::tree::SyntaxTree;
|
||||
|
||||
/// Transforms source code into typesetted layouts.
|
||||
///
|
||||
@ -59,11 +64,11 @@ pub struct Typesetter {
|
||||
|
||||
impl Typesetter {
|
||||
/// Create a new typesetter.
|
||||
pub fn new(loader: SharedFontLoader) -> Typesetter {
|
||||
Typesetter {
|
||||
pub fn new(loader: SharedFontLoader) -> Self {
|
||||
Self {
|
||||
loader,
|
||||
style: LayoutStyle::default(),
|
||||
parse_state: ParseState { scope: Scope::with_std() },
|
||||
parse_state: ParseState { scope: crate::library::std() },
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,9 +105,9 @@ impl Typesetter {
|
||||
expansion: LayoutExpansion::new(true, true),
|
||||
}],
|
||||
repeat: true,
|
||||
axes: LayoutAxes::new(LTT, TTB),
|
||||
axes: LayoutAxes::new(LTR, TTB),
|
||||
align: LayoutAlign::new(Start, Start),
|
||||
nested: false,
|
||||
root: true,
|
||||
},
|
||||
).await
|
||||
}
|
||||
@ -119,7 +124,7 @@ impl Typesetter {
|
||||
/// A dynamic future type which allows recursive invocation of async functions
|
||||
/// when used as the return type. This is also how the async trait functions
|
||||
/// work internally.
|
||||
pub type DynFuture<'a, T> = Pin<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));
|
||||
}
|
||||
|
@ -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)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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![]
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
13
src/paper.rs
13
src/paper.rs
@ -16,7 +16,7 @@ pub struct Paper {
|
||||
|
||||
impl Paper {
|
||||
/// The paper with the given name.
|
||||
pub fn from_name(name: &str) -> Option<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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
19
src/style.rs
19
src/style.rs
@ -1,7 +1,10 @@
|
||||
//! Styles for text and pages.
|
||||
|
||||
use fontdock::{fallback, FallbackTree, FontVariant, FontStyle, FontWeight, FontWidth};
|
||||
use crate::geom::{Size, Margins, Value4};
|
||||
use fontdock::{
|
||||
fallback, FallbackTree, FontStyle, FontVariant, FontWeight, FontWidth,
|
||||
};
|
||||
|
||||
use crate::geom::{Margins, Size, Value4};
|
||||
use crate::length::{Length, ScaleLength};
|
||||
use crate::paper::{Paper, PaperClass, PAPER_A4};
|
||||
|
||||
@ -62,8 +65,8 @@ impl TextStyle {
|
||||
}
|
||||
|
||||
impl Default for TextStyle {
|
||||
fn default() -> TextStyle {
|
||||
TextStyle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
fallback: fallback! {
|
||||
list: ["sans-serif"],
|
||||
classes: {
|
||||
@ -105,8 +108,8 @@ pub struct PageStyle {
|
||||
|
||||
impl PageStyle {
|
||||
/// The default page style for the given paper.
|
||||
pub fn new(paper: Paper) -> PageStyle {
|
||||
PageStyle {
|
||||
pub fn new(paper: Paper) -> Self {
|
||||
Self {
|
||||
class: paper.class,
|
||||
size: paper.size(),
|
||||
margins: Value4::with_all(None),
|
||||
@ -127,7 +130,7 @@ impl PageStyle {
|
||||
}
|
||||
|
||||
impl Default for PageStyle {
|
||||
fn default() -> PageStyle {
|
||||
PageStyle::new(PAPER_A4)
|
||||
fn default() -> Self {
|
||||
Self::new(PAPER_A4)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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;
|
||||
|
@ -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)],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>)
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
|
Loading…
x
Reference in New Issue
Block a user