mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Run rustfmt 🚿
This commit is contained in:
parent
5c04185892
commit
7c0899b537
@ -1,14 +1,13 @@
|
||||
use std::env;
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, BufWriter};
|
||||
use std::io::{BufWriter, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use typst::Typesetter;
|
||||
use typst::export::pdf::PdfExporter;
|
||||
use typst::toddle::query::FileSystemFontProvider;
|
||||
|
||||
use typst::Typesetter;
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = run() {
|
||||
@ -26,11 +25,16 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let source_path = Path::new(&args[1]);
|
||||
|
||||
// Compute the output filename from the input filename by replacing the extension.
|
||||
// Compute the output filename from the input filename by replacing the
|
||||
// extension.
|
||||
let dest_path = if args.len() <= 2 {
|
||||
let stem = source_path.file_stem().ok_or_else(|| "missing destation file name")?;
|
||||
let stem = source_path
|
||||
.file_stem()
|
||||
.ok_or_else(|| "missing destation file name")?;
|
||||
|
||||
let base = source_path.parent().ok_or_else(|| "missing destation folder")?;
|
||||
let base = source_path
|
||||
.parent()
|
||||
.ok_or_else(|| "missing destation folder")?;
|
||||
|
||||
base.join(format!("{}.pdf", stem.to_string_lossy()))
|
||||
} else {
|
||||
@ -43,7 +47,9 @@ fn run() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
let mut src = String::new();
|
||||
let mut source_file = File::open(source_path).map_err(|_| "failed to open source file")?;
|
||||
source_file.read_to_string(&mut src).map_err(|_| "failed to read from source file")?;
|
||||
source_file
|
||||
.read_to_string(&mut src)
|
||||
.map_err(|_| "failed to read from source file")?;
|
||||
|
||||
// Create a typesetter with a font provider that provides the default fonts.
|
||||
let mut typesetter = Typesetter::new();
|
||||
|
@ -3,21 +3,22 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use tide::{PdfWriter, Ref, Rect, Version, Trailer};
|
||||
use tide::content::Content;
|
||||
use tide::doc::{Catalog, PageTree, Page, Resource, Text};
|
||||
use tide::font::{Type0Font, CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags};
|
||||
use tide::font::{GlyphUnit, CMap, CMapEncoding, WidthRecord, FontStream};
|
||||
use tide::doc::{Catalog, Page, PageTree, Resource, Text};
|
||||
use tide::font::{CIDFont, CIDFontType, CIDSystemInfo, FontDescriptor, FontFlags, Type0Font};
|
||||
use tide::font::{CMap, CMapEncoding, FontStream, GlyphUnit, WidthRecord};
|
||||
use tide::{PdfWriter, Rect, Ref, Trailer, Version};
|
||||
|
||||
use toddle::tables::{Header, Post, OS2, HorizontalMetrics, CharMap, Name, NameEntry, MacStyleFlags};
|
||||
use toddle::font::OwnedFont;
|
||||
use toddle::query::SharedFontLoader;
|
||||
use toddle::tables::{
|
||||
CharMap, Header, HorizontalMetrics, MacStyleFlags, Name, NameEntry, Post, OS2,
|
||||
};
|
||||
use toddle::Error as FontError;
|
||||
|
||||
use crate::layout::{MultiLayout, Layout, LayoutAction};
|
||||
use crate::layout::{Layout, LayoutAction, MultiLayout};
|
||||
use crate::size::{Size, Size2D};
|
||||
|
||||
|
||||
/// Exports layouts into _PDFs_.
|
||||
#[derive(Debug)]
|
||||
pub struct PdfExporter {}
|
||||
@ -29,10 +30,16 @@ impl PdfExporter {
|
||||
PdfExporter {}
|
||||
}
|
||||
|
||||
/// Export a finished layouts into a writer. Returns how many bytes were written.
|
||||
/// Export a finished layouts into a writer. Returns how many bytes were
|
||||
/// written.
|
||||
#[inline]
|
||||
pub fn export<W: Write>(&self, layout: &MultiLayout, loader: &SharedFontLoader, target: W)
|
||||
-> PdfResult<usize> {
|
||||
pub fn export<W: Write>(
|
||||
&self,
|
||||
layout: &MultiLayout,
|
||||
loader: &SharedFontLoader,
|
||||
target: W,
|
||||
) -> PdfResult<usize>
|
||||
{
|
||||
let mut engine = PdfEngine::new(layout, loader, target)?;
|
||||
engine.write()
|
||||
}
|
||||
@ -59,8 +66,12 @@ struct Offsets {
|
||||
|
||||
impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
/// Create a new _PDF_ engine.
|
||||
fn new(layout: &'d MultiLayout, loader: &SharedFontLoader, target: W)
|
||||
-> PdfResult<PdfEngine<'d, W>> {
|
||||
fn new(
|
||||
layout: &'d MultiLayout,
|
||||
loader: &SharedFontLoader,
|
||||
target: W,
|
||||
) -> PdfResult<PdfEngine<'d, W>>
|
||||
{
|
||||
// Create a subsetted PDF font for each font in the layout.
|
||||
let mut font_remap = HashMap::new();
|
||||
let fonts = {
|
||||
@ -71,24 +82,26 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
for boxed in &layout.layouts {
|
||||
for action in &boxed.actions {
|
||||
match action {
|
||||
LayoutAction::WriteText(string) => {
|
||||
chars.entry(font)
|
||||
.or_insert_with(HashSet::new)
|
||||
.extend(string.chars())
|
||||
},
|
||||
LayoutAction::WriteText(string) => chars
|
||||
.entry(font)
|
||||
.or_insert_with(HashSet::new)
|
||||
.extend(string.chars()),
|
||||
LayoutAction::SetFont(id, _) => {
|
||||
font = *id;
|
||||
let new_id = font_remap.len();
|
||||
font_remap.entry(font).or_insert(new_id);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the fonts into a vector in the order of the values in the remapping.
|
||||
let mut loader = loader.borrow_mut();
|
||||
let mut order = font_remap.iter().map(|(&old, &new)| (old, new)).collect::<Vec<_>>();
|
||||
let mut order = font_remap
|
||||
.iter()
|
||||
.map(|(&old, &new)| (old, new))
|
||||
.collect::<Vec<_>>();
|
||||
order.sort_by_key(|&(_, new)| new);
|
||||
|
||||
let mut fonts = vec![];
|
||||
@ -96,8 +109,10 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
let font = loader.get_with_index(index);
|
||||
let subsetted = font.subsetted(
|
||||
chars[&index].iter().cloned(),
|
||||
&["name", "OS/2", "post", "head", "hhea", "hmtx", "maxp",
|
||||
"cmap", "cvt ", "fpgm", "prep", "loca", "glyf"][..]
|
||||
&[
|
||||
"name", "OS/2", "post", "head", "hhea", "hmtx", "maxp", "cmap", "cvt ",
|
||||
"fpgm", "prep", "loca", "glyf",
|
||||
][..],
|
||||
)?;
|
||||
fonts.push(OwnedFont::from_bytes(subsetted)?);
|
||||
}
|
||||
@ -111,7 +126,13 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
let pages = (page_tree + 1, page_tree + layout.layouts.len() as Ref);
|
||||
let contents = (pages.1 + 1, pages.1 + layout.layouts.len() as Ref);
|
||||
let font_offsets = (contents.1 + 1, contents.1 + 5 * fonts.len() as Ref);
|
||||
let offsets = Offsets { catalog, page_tree, pages, contents, fonts: font_offsets };
|
||||
let offsets = Offsets {
|
||||
catalog,
|
||||
page_tree,
|
||||
pages,
|
||||
contents,
|
||||
fonts: font_offsets,
|
||||
};
|
||||
|
||||
Ok(PdfEngine {
|
||||
writer: PdfWriter::new(target),
|
||||
@ -129,32 +150,43 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
self.write_pages()?;
|
||||
self.write_fonts()?;
|
||||
self.writer.write_xref_table()?;
|
||||
self.writer.write_trailer(Trailer::new(self.offsets.catalog))?;
|
||||
self.writer
|
||||
.write_trailer(Trailer::new(self.offsets.catalog))?;
|
||||
Ok(self.writer.written())
|
||||
}
|
||||
|
||||
/// Write the document catalog and page tree.
|
||||
fn write_page_tree(&mut self) -> PdfResult<()> {
|
||||
// The document catalog
|
||||
self.writer.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?;
|
||||
self.writer
|
||||
.write_obj(self.offsets.catalog, &Catalog::new(self.offsets.page_tree))?;
|
||||
|
||||
// The font resources
|
||||
let offset = self.offsets.fonts.0;
|
||||
let fonts = (0 .. self.fonts.len())
|
||||
.map(|i| Resource::Font((i + 1) as u32, offset + 5 * i as u32));
|
||||
let fonts =
|
||||
(0..self.fonts.len()).map(|i| Resource::Font((i + 1) as u32, offset + 5 * i as u32));
|
||||
|
||||
// The root page tree
|
||||
self.writer.write_obj(self.offsets.page_tree, PageTree::new()
|
||||
.kids(ids(self.offsets.pages))
|
||||
.resources(fonts)
|
||||
self.writer.write_obj(
|
||||
self.offsets.page_tree,
|
||||
PageTree::new()
|
||||
.kids(ids(self.offsets.pages))
|
||||
.resources(fonts),
|
||||
)?;
|
||||
|
||||
// The page objects
|
||||
for (id, page) in ids(self.offsets.pages).zip(&self.layout.layouts) {
|
||||
let rect = Rect::new(0.0, 0.0, page.dimensions.x.to_pt(), page.dimensions.y.to_pt());
|
||||
self.writer.write_obj(id, Page::new(self.offsets.page_tree)
|
||||
.media_box(rect)
|
||||
.contents(ids(self.offsets.contents))
|
||||
let rect = Rect::new(
|
||||
0.0,
|
||||
0.0,
|
||||
page.dimensions.x.to_pt(),
|
||||
page.dimensions.y.to_pt(),
|
||||
);
|
||||
self.writer.write_obj(
|
||||
id,
|
||||
Page::new(self.offsets.page_tree)
|
||||
.media_box(rect)
|
||||
.contents(ids(self.offsets.contents)),
|
||||
)?;
|
||||
}
|
||||
|
||||
@ -202,8 +234,8 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
|
||||
// Write the text.
|
||||
text.tj(self.fonts[active_font.0].encode_text(&string)?);
|
||||
},
|
||||
LayoutAction::DebugBox(_, _) => {},
|
||||
}
|
||||
LayoutAction::DebugBox(_, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,18 +249,21 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
let mut id = self.offsets.fonts.0;
|
||||
|
||||
for font in &mut self.fonts {
|
||||
let name = font.read_table::<Name>()?
|
||||
let name = font
|
||||
.read_table::<Name>()?
|
||||
.get_decoded(NameEntry::PostScriptName)
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let base_font = format!("ABCDEF+{}", name);
|
||||
|
||||
// Write the base font object referencing the CID font.
|
||||
self.writer.write_obj(id,
|
||||
self.writer.write_obj(
|
||||
id,
|
||||
Type0Font::new(
|
||||
base_font.clone(),
|
||||
CMapEncoding::Predefined("Identity-H".to_owned()),
|
||||
id + 1
|
||||
).to_unicode(id + 3)
|
||||
id + 1,
|
||||
)
|
||||
.to_unicode(id + 3),
|
||||
)?;
|
||||
|
||||
// Extract information from the head table.
|
||||
@ -252,19 +287,22 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
// Transform the width into PDF units.
|
||||
let widths: Vec<_> = font
|
||||
.read_table::<HorizontalMetrics>()?
|
||||
.metrics.iter()
|
||||
.metrics
|
||||
.iter()
|
||||
.map(|m| font_unit_to_glyph_unit(m.advance_width as f32))
|
||||
.collect();
|
||||
|
||||
// Write the CID font referencing the font descriptor.
|
||||
let system_info = CIDSystemInfo::new("Adobe", "Identity", 0);
|
||||
self.writer.write_obj(id + 1,
|
||||
self.writer.write_obj(
|
||||
id + 1,
|
||||
CIDFont::new(
|
||||
CIDFontType::Type2,
|
||||
base_font.clone(),
|
||||
system_info.clone(),
|
||||
id + 2,
|
||||
).widths(vec![WidthRecord::start(0, widths)])
|
||||
)
|
||||
.widths(vec![WidthRecord::start(0, widths)]),
|
||||
)?;
|
||||
|
||||
// Extract information from the post table.
|
||||
@ -284,24 +322,31 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
let os2 = font.read_table::<OS2>()?;
|
||||
|
||||
// Write the font descriptor (contains the global information about the font).
|
||||
self.writer.write_obj(id + 2,
|
||||
self.writer.write_obj(
|
||||
id + 2,
|
||||
FontDescriptor::new(base_font, flags, italic_angle)
|
||||
.font_bbox(bounding_box)
|
||||
.ascent(font_unit_to_glyph_unit(os2.s_typo_ascender as f32))
|
||||
.descent(font_unit_to_glyph_unit(os2.s_typo_descender as f32))
|
||||
.cap_height(font_unit_to_glyph_unit(os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32))
|
||||
.cap_height(font_unit_to_glyph_unit(
|
||||
os2.s_cap_height.unwrap_or(os2.s_typo_ascender) as f32,
|
||||
))
|
||||
.stem_v((10.0 + 0.244 * (os2.us_weight_class as f32 - 50.0)) as GlyphUnit)
|
||||
.font_file_2(id + 4)
|
||||
.font_file_2(id + 4),
|
||||
)?;
|
||||
|
||||
// Write the CMap, which maps glyphs to unicode codepoints.
|
||||
let mapping = font.read_table::<CharMap>()?
|
||||
.mapping.iter()
|
||||
let mapping = font
|
||||
.read_table::<CharMap>()?
|
||||
.mapping
|
||||
.iter()
|
||||
.map(|(&c, &cid)| (cid, c));
|
||||
self.writer.write_obj(id + 3, &CMap::new("Custom", system_info, mapping))?;
|
||||
self.writer
|
||||
.write_obj(id + 3, &CMap::new("Custom", system_info, mapping))?;
|
||||
|
||||
// Finally write the subsetted font program.
|
||||
self.writer.write_obj(id + 4, &FontStream::new(font.data().get_ref()))?;
|
||||
self.writer
|
||||
.write_obj(id + 4, &FontStream::new(font.data().get_ref()))?;
|
||||
|
||||
id += 5;
|
||||
}
|
||||
@ -311,8 +356,8 @@ impl<'d, W: Write> PdfEngine<'d, W> {
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
|
||||
/// The error type for _PDF_ creation.
|
||||
|
50
src/func.rs
50
src/func.rs
@ -6,27 +6,27 @@ use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use toddle::query::FontClass;
|
||||
|
||||
use crate::layout::{Layout, MultiLayout , LayoutContext, LayoutResult};
|
||||
use crate::layout::{Layout, LayoutContext, LayoutResult, MultiLayout};
|
||||
use crate::parsing::{ParseContext, ParseResult};
|
||||
use crate::syntax::{SyntaxTree, FuncHeader};
|
||||
|
||||
use crate::syntax::{FuncHeader, SyntaxTree};
|
||||
|
||||
/// Typesetting function types.
|
||||
///
|
||||
/// These types have to be able to parse tokens into themselves and store the relevant information
|
||||
/// from the parsing to do their role in typesetting later.
|
||||
/// These types have to be able to parse tokens into themselves and store the
|
||||
/// relevant information from the parsing to do their role in typesetting later.
|
||||
///
|
||||
/// The trait `FunctionBounds` is automatically implemented for types which can be used as
|
||||
/// functions, that is they fulfill the bounds `Debug + PartialEq + 'static`.
|
||||
/// The trait `FunctionBounds` is automatically implemented for types which can
|
||||
/// be used as functions, that is they fulfill the bounds `Debug + PartialEq +
|
||||
/// 'static`.
|
||||
pub trait Function: FunctionBounds {
|
||||
/// Parse the header and body into this function given a context.
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized;
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized;
|
||||
|
||||
/// Layout this function given a context.
|
||||
///
|
||||
/// Returns optionally the resulting layout and a new context if changes to the context should
|
||||
/// be made.
|
||||
/// Returns optionally the resulting layout and a new context if changes to
|
||||
/// the context should be made.
|
||||
fn layout(&self, ctx: LayoutContext) -> LayoutResult<FuncCommands>;
|
||||
}
|
||||
|
||||
@ -39,15 +39,13 @@ impl PartialEq for dyn Function {
|
||||
/// A sequence of commands requested for execution by a function.
|
||||
#[derive(Debug)]
|
||||
pub struct FuncCommands<'a> {
|
||||
pub commands: Vec<Command<'a>>
|
||||
pub commands: Vec<Command<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FuncCommands<'a> {
|
||||
/// Create an empty command list.
|
||||
pub fn new() -> FuncCommands<'a> {
|
||||
FuncCommands {
|
||||
commands: vec![],
|
||||
}
|
||||
FuncCommands { commands: vec![] }
|
||||
}
|
||||
|
||||
/// Add a command to the sequence.
|
||||
@ -79,10 +77,11 @@ pub enum Command<'a> {
|
||||
ToggleStyleClass(FontClass),
|
||||
}
|
||||
|
||||
/// A helper trait that describes requirements for types that can implement [`Function`].
|
||||
/// A helper trait that describes requirements for types that can implement
|
||||
/// [`Function`].
|
||||
///
|
||||
/// Automatically implemented for all types which fulfill to the bounds `Debug + PartialEq +
|
||||
/// 'static`. There should be no need to implement this manually.
|
||||
/// Automatically implemented for all types which fulfill to the bounds `Debug +
|
||||
/// PartialEq + 'static`. There should be no need to implement this manually.
|
||||
pub trait FunctionBounds: Debug {
|
||||
/// Cast self into `Any`.
|
||||
fn help_cast_as_any(&self) -> &dyn Any;
|
||||
@ -91,7 +90,9 @@ pub trait FunctionBounds: Debug {
|
||||
fn help_eq(&self, other: &dyn Function) -> bool;
|
||||
}
|
||||
|
||||
impl<T> FunctionBounds for T where T: Debug + PartialEq + 'static {
|
||||
impl<T> FunctionBounds for T
|
||||
where T: Debug + PartialEq + 'static
|
||||
{
|
||||
fn help_cast_as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
@ -111,13 +112,14 @@ pub struct Scope {
|
||||
}
|
||||
|
||||
/// A function which parses a function invocation into a function type.
|
||||
type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext)
|
||||
-> ParseResult<Box<dyn Function>>;
|
||||
type ParseFunc = dyn Fn(&FuncHeader, Option<&str>, ParseContext) -> ParseResult<Box<dyn Function>>;
|
||||
|
||||
impl Scope {
|
||||
/// Create a new empty scope.
|
||||
pub fn new() -> Scope {
|
||||
Scope { parsers: HashMap::new() }
|
||||
Scope {
|
||||
parsers: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new scope with the standard functions contained.
|
||||
@ -129,9 +131,7 @@ impl Scope {
|
||||
pub fn add<F: Function + 'static>(&mut self, name: &str) {
|
||||
self.parsers.insert(
|
||||
name.to_owned(),
|
||||
Box::new(|h, b, c| {
|
||||
F::parse(h, b, c).map(|func| Box::new(func) as Box<dyn Function>)
|
||||
})
|
||||
Box::new(|h, b, c| F::parse(h, b, c).map(|func| Box::new(func) as Box<dyn Function>)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,10 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::io::{self, Write};
|
||||
|
||||
use crate::size::Size2D;
|
||||
use super::Layout;
|
||||
use crate::size::Size2D;
|
||||
use LayoutAction::*;
|
||||
|
||||
|
||||
/// A layouting action.
|
||||
#[derive(Clone)]
|
||||
pub enum LayoutAction {
|
||||
@ -30,8 +29,14 @@ impl LayoutAction {
|
||||
MoveAbsolute(s) => write!(f, "m {:.4} {:.4}", s.x.to_pt(), s.y.to_pt()),
|
||||
SetFont(i, s) => write!(f, "f {} {}", i, s),
|
||||
WriteText(s) => write!(f, "w {}", s),
|
||||
DebugBox(p, s) => write!(f, "b {} {} {} {}",
|
||||
p.x.to_pt(), p.y.to_pt(), s.x.to_pt(), s.y.to_pt())
|
||||
DebugBox(p, s) => write!(
|
||||
f,
|
||||
"b {} {} {} {}",
|
||||
p.x.to_pt(),
|
||||
p.y.to_pt(),
|
||||
s.x.to_pt(),
|
||||
s.y.to_pt()
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -81,7 +86,7 @@ impl LayoutActionList {
|
||||
|
||||
SetFont(index, size) if (index, size) != self.active_font => {
|
||||
self.next_font = Some((index, size));
|
||||
},
|
||||
}
|
||||
|
||||
_ => {
|
||||
if let Some(target) = self.next_pos.take() {
|
||||
@ -92,19 +97,21 @@ impl LayoutActionList {
|
||||
}
|
||||
|
||||
self.actions.push(action);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a series of actions.
|
||||
pub fn extend<I>(&mut self, actions: I) where I: IntoIterator<Item=LayoutAction> {
|
||||
pub fn extend<I>(&mut self, actions: I)
|
||||
where I: IntoIterator<Item = LayoutAction> {
|
||||
for action in actions.into_iter() {
|
||||
self.add(action);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add all actions from a box layout at a position. A move to the position
|
||||
/// is generated and all moves inside the box layout are translated as necessary.
|
||||
/// is generated and all moves inside the box layout are translated as
|
||||
/// necessary.
|
||||
pub fn add_box(&mut self, position: Size2D, layout: Layout) {
|
||||
if let Some(target) = self.next_pos.take() {
|
||||
self.actions.push(MoveAbsolute(target));
|
||||
@ -114,7 +121,8 @@ impl LayoutActionList {
|
||||
self.origin = position;
|
||||
|
||||
if layout.debug_render {
|
||||
self.actions.push(LayoutAction::DebugBox(position, layout.dimensions));
|
||||
self.actions
|
||||
.push(LayoutAction::DebugBox(position, layout.dimensions));
|
||||
}
|
||||
|
||||
self.extend(layout.actions);
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
|
||||
/// Finishes a flex layout by justifying the positions of the individual boxes.
|
||||
#[derive(Debug)]
|
||||
pub struct FlexLayouter {
|
||||
@ -32,7 +31,8 @@ enum FlexUnit {
|
||||
/// A content unit to be arranged flexibly.
|
||||
Boxed(Layout),
|
||||
/// A unit which acts as glue between two [`FlexUnit::Boxed`] units and
|
||||
/// is only present if there was no flow break in between the two surrounding boxes.
|
||||
/// is only present if there was no flow break in between the two
|
||||
/// surrounding boxes.
|
||||
Glue(Layout),
|
||||
}
|
||||
|
||||
@ -107,7 +107,9 @@ impl FlexLayouter {
|
||||
|
||||
/// Layout the box.
|
||||
fn boxed(&mut self, boxed: Layout) -> LayoutResult<()> {
|
||||
let last_glue_x = self.last_glue.as_ref()
|
||||
let last_glue_x = self
|
||||
.last_glue
|
||||
.as_ref()
|
||||
.map(|g| g.dimensions.x)
|
||||
.unwrap_or(Size::zero());
|
||||
|
||||
@ -157,7 +159,7 @@ impl FlexLayouter {
|
||||
// Right align everything by shifting it right by the
|
||||
// amount of space left to the right of the line.
|
||||
cursor + remaining_space
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
self.actions.add_box(position, layout);
|
||||
@ -173,7 +175,8 @@ impl FlexLayouter {
|
||||
|
||||
self.dimensions.y += self.line_metrics.y;
|
||||
|
||||
// Reset the cursor the left and move down by the line and the inter-line spacing.
|
||||
// Reset the cursor the left and move down by the line and the inter-line
|
||||
// spacing.
|
||||
self.cursor.x = self.ctx.space.padding.left;
|
||||
self.cursor.y += self.line_metrics.y + self.ctx.flex_spacing;
|
||||
|
||||
|
@ -4,24 +4,23 @@ use std::borrow::Cow;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
|
||||
use toddle::query::{SharedFontLoader, FontClass};
|
||||
use toddle::query::{FontClass, SharedFontLoader};
|
||||
use toddle::Error as FontError;
|
||||
|
||||
use crate::func::Command;
|
||||
use crate::size::{Size, Size2D, SizeBox};
|
||||
use crate::syntax::{SyntaxTree, Node, FuncCall};
|
||||
use crate::style::TextStyle;
|
||||
use crate::syntax::{FuncCall, Node, SyntaxTree};
|
||||
|
||||
mod text;
|
||||
mod stacked;
|
||||
mod flex;
|
||||
mod actions;
|
||||
mod flex;
|
||||
mod stacked;
|
||||
mod text;
|
||||
|
||||
pub use actions::{LayoutAction, LayoutActionList};
|
||||
pub use flex::{FlexContext, FlexLayouter};
|
||||
pub use stacked::{StackContext, StackLayouter};
|
||||
pub use text::{layout_text, TextContext};
|
||||
pub use flex::{FlexLayouter, FlexContext};
|
||||
pub use stacked::{StackLayouter, StackContext};
|
||||
|
||||
|
||||
/// A box layout has a fixed width and height and composes of actions.
|
||||
#[derive(Debug, Clone)]
|
||||
@ -37,7 +36,12 @@ pub struct Layout {
|
||||
impl Layout {
|
||||
/// Serialize this layout into an output buffer.
|
||||
pub fn serialize<W: Write>(&self, f: &mut W) -> io::Result<()> {
|
||||
writeln!(f, "{:.4} {:.4}", self.dimensions.x.to_pt(), self.dimensions.y.to_pt())?;
|
||||
writeln!(
|
||||
f,
|
||||
"{:.4} {:.4}",
|
||||
self.dimensions.x.to_pt(),
|
||||
self.dimensions.y.to_pt()
|
||||
)?;
|
||||
for action in &self.actions {
|
||||
action.serialize(f)?;
|
||||
writeln!(f)?;
|
||||
@ -55,9 +59,7 @@ pub struct MultiLayout {
|
||||
impl MultiLayout {
|
||||
/// Create an empty multibox layout.
|
||||
pub fn new() -> MultiLayout {
|
||||
MultiLayout {
|
||||
layouts: vec![],
|
||||
}
|
||||
MultiLayout { layouts: vec![] }
|
||||
}
|
||||
|
||||
/// Extract a single sublayout and panic if this layout does not have
|
||||
@ -158,7 +160,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
},
|
||||
flex_spacing: (ctx.style.line_spacing - 1.0) * Size::pt(ctx.style.font_size),
|
||||
}),
|
||||
style: Cow::Borrowed(ctx.style)
|
||||
style: Cow::Borrowed(ctx.style),
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,7 +177,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
if !self.flex_layouter.is_empty() {
|
||||
self.layout_text(" ", true)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Finish the current flex layout and add it to the box layouter.
|
||||
Node::Newline => {
|
||||
@ -186,7 +188,7 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
let size = Size::pt(self.style.font_size)
|
||||
* (self.style.line_spacing * self.style.paragraph_spacing - 1.0);
|
||||
self.stack_layouter.add_space(size)?;
|
||||
},
|
||||
}
|
||||
|
||||
// Toggle the text styles.
|
||||
Node::ToggleItalics => self.style.to_mut().toggle_class(FontClass::Italic),
|
||||
@ -208,16 +210,19 @@ impl<'a, 'p> Layouter<'a, 'p> {
|
||||
}
|
||||
|
||||
Ok(MultiLayout {
|
||||
layouts: vec![self.stack_layouter.finish()]
|
||||
layouts: vec![self.stack_layouter.finish()],
|
||||
})
|
||||
}
|
||||
|
||||
/// Layout a piece of text into a box.
|
||||
fn layout_text(&mut self, text: &str, glue: bool) -> LayoutResult<()> {
|
||||
let boxed = layout_text(text, TextContext {
|
||||
loader: &self.ctx.loader,
|
||||
style: &self.style,
|
||||
})?;
|
||||
let boxed = layout_text(
|
||||
text,
|
||||
TextContext {
|
||||
loader: &self.ctx.loader,
|
||||
style: &self.style,
|
||||
},
|
||||
)?;
|
||||
|
||||
if glue {
|
||||
self.flex_layouter.add_glue(boxed);
|
||||
|
@ -1,6 +1,5 @@
|
||||
use super::*;
|
||||
|
||||
|
||||
/// Layouts boxes block-style.
|
||||
#[derive(Debug)]
|
||||
pub struct StackLayouter {
|
||||
@ -29,10 +28,13 @@ impl StackLayouter {
|
||||
Alignment::Right => Size2D::with_x(space.usable().x),
|
||||
},
|
||||
usable: space.usable(),
|
||||
cursor: Size2D::new(match ctx.space.alignment {
|
||||
Alignment::Left => space.padding.left,
|
||||
Alignment::Right => space.dimensions.x - space.padding.right,
|
||||
}, space.padding.top),
|
||||
cursor: Size2D::new(
|
||||
match ctx.space.alignment {
|
||||
Alignment::Left => space.padding.left,
|
||||
Alignment::Right => space.dimensions.x - space.padding.right,
|
||||
},
|
||||
space.padding.top,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
use toddle::query::{FontQuery, SharedFontLoader};
|
||||
use toddle::tables::{Header, CharMap, HorizontalMetrics};
|
||||
use toddle::tables::{CharMap, Header, HorizontalMetrics};
|
||||
|
||||
use crate::size::{Size, Size2D};
|
||||
use super::*;
|
||||
|
||||
use crate::size::{Size, Size2D};
|
||||
|
||||
/// The context for text layouting.
|
||||
#[derive(Copy, Clone)]
|
||||
@ -53,7 +52,8 @@ pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
|
||||
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
|
||||
|
||||
// Add the char width to the total box width.
|
||||
let glyph = font.read_table::<CharMap>()?
|
||||
let glyph = font
|
||||
.read_table::<CharMap>()?
|
||||
.get(character)
|
||||
.expect("layout text: font should have char");
|
||||
|
||||
@ -61,7 +61,7 @@ pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
|
||||
font.read_table::<HorizontalMetrics>()?
|
||||
.get(glyph)
|
||||
.expect("layout text: font should have glyph")
|
||||
.advance_width as f32
|
||||
.advance_width as f32,
|
||||
);
|
||||
|
||||
let char_width = glyph_width * ctx.style.font_size;
|
||||
|
48
src/lib.rs
48
src/lib.rs
@ -1,26 +1,28 @@
|
||||
//! The compiler for the _Typst_ typesetting language.
|
||||
//!
|
||||
//! # Steps
|
||||
//! - **Parsing:** The parsing step first transforms a plain string into an [iterator of
|
||||
//! tokens](crate::parsing::Tokens). Then parser constructs a syntax tree from the token stream.
|
||||
//! The structures describing the tree can be found in the [syntax]. Dynamic functions parse
|
||||
//! their own bodies themselves.
|
||||
//! - **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] module.
|
||||
//! - **Exporting:** The finished document can then be exported into supported formats. Submodules
|
||||
//! for the supported formats are located in the [export] module. Currently the only supported
|
||||
//! format is _PDF_.
|
||||
//! - **Parsing:** The parsing step first transforms a plain string into an
|
||||
//! [iterator of tokens](crate::parsing::Tokens). Then parser constructs a
|
||||
//! syntax tree from the token stream. The structures describing the tree can
|
||||
//! be found in the [syntax]. Dynamic functions parse their own bodies
|
||||
//! themselves.
|
||||
//! - **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] module.
|
||||
//! - **Exporting:** The finished document can then be exported into supported
|
||||
//! formats. Submodules for the supported formats are located in the [export]
|
||||
//! module. Currently the only supported format is _PDF_.
|
||||
|
||||
pub extern crate toddle;
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use toddle::query::{FontLoader, SharedFontLoader, FontProvider};
|
||||
use toddle::query::{FontLoader, FontProvider, SharedFontLoader};
|
||||
|
||||
use crate::func::Scope;
|
||||
use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||
use crate::layout::{layout_tree, LayoutContext, MultiLayout};
|
||||
use crate::layout::{LayoutSpace, Alignment, LayoutError, LayoutResult};
|
||||
use crate::layout::{Alignment, LayoutError, LayoutResult, LayoutSpace};
|
||||
use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||
use crate::style::{PageStyle, TextStyle};
|
||||
use crate::syntax::SyntaxTree;
|
||||
|
||||
@ -29,12 +31,11 @@ mod macros;
|
||||
pub mod export;
|
||||
pub mod func;
|
||||
pub mod layout;
|
||||
pub mod library;
|
||||
pub mod parsing;
|
||||
pub mod size;
|
||||
pub mod style;
|
||||
pub mod syntax;
|
||||
pub mod library;
|
||||
|
||||
|
||||
/// Transforms source code into typesetted documents.
|
||||
///
|
||||
@ -73,7 +74,8 @@ impl<'p> Typesetter<'p> {
|
||||
|
||||
/// Add a font provider to the context of this typesetter.
|
||||
#[inline]
|
||||
pub fn add_font_provider<P: 'p>(&mut self, provider: P) where P: FontProvider {
|
||||
pub fn add_font_provider<P: 'p>(&mut self, provider: P)
|
||||
where P: FontProvider {
|
||||
self.loader.get_mut().add_provider(provider);
|
||||
}
|
||||
|
||||
@ -98,12 +100,15 @@ impl<'p> Typesetter<'p> {
|
||||
shrink_to_fit: false,
|
||||
};
|
||||
|
||||
let pages = layout_tree(&tree, LayoutContext {
|
||||
loader: &self.loader,
|
||||
style: &self.text_style,
|
||||
space,
|
||||
extra_space: Some(space),
|
||||
})?;
|
||||
let pages = layout_tree(
|
||||
&tree,
|
||||
LayoutContext {
|
||||
loader: &self.loader,
|
||||
style: &self.text_style,
|
||||
space,
|
||||
extra_space: Some(space),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(pages)
|
||||
}
|
||||
@ -116,7 +121,6 @@ impl<'p> Typesetter<'p> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// The general error type for typesetting.
|
||||
pub enum TypesetError {
|
||||
/// An error that occured while parsing.
|
||||
|
@ -1,7 +1,6 @@
|
||||
use super::prelude::*;
|
||||
use crate::layout::Alignment;
|
||||
|
||||
|
||||
/// Allows to align content in different ways.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct AlignFunc {
|
||||
@ -10,9 +9,8 @@ pub struct AlignFunc {
|
||||
}
|
||||
|
||||
impl Function for AlignFunc {
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized {
|
||||
|
||||
fn parse(header: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if header.args.len() != 1 || !header.kwargs.is_empty() {
|
||||
return err("expected exactly one positional argument specifying the alignment");
|
||||
}
|
||||
@ -24,7 +22,10 @@ impl Function for AlignFunc {
|
||||
s => return err(format!("invalid alignment specifier: '{}'", s)),
|
||||
}
|
||||
} else {
|
||||
return err(format!("expected alignment specifier, found: '{}'", header.args[0]));
|
||||
return err(format!(
|
||||
"expected alignment specifier, found: '{}'",
|
||||
header.args[0]
|
||||
));
|
||||
};
|
||||
|
||||
let body = if let Some(body) = body {
|
||||
|
@ -7,11 +7,11 @@ mod styles;
|
||||
|
||||
/// Useful imports for creating your own functions.
|
||||
pub mod prelude {
|
||||
pub use crate::syntax::{SyntaxTree, FuncHeader, Expression};
|
||||
pub use crate::parsing::{parse, ParseContext, ParseResult, ParseError};
|
||||
pub use crate::layout::{layout_tree, LayoutContext, MultiLayout, Layout};
|
||||
pub use crate::layout::{LayoutResult, LayoutError};
|
||||
pub use crate::func::{Function, Command, FuncCommands};
|
||||
pub use crate::func::{Command, FuncCommands, Function};
|
||||
pub use crate::layout::{layout_tree, Layout, LayoutContext, MultiLayout};
|
||||
pub use crate::layout::{LayoutError, LayoutResult};
|
||||
pub use crate::parsing::{parse, ParseContext, ParseError, ParseResult};
|
||||
pub use crate::syntax::{Expression, FuncHeader, SyntaxTree};
|
||||
|
||||
pub fn err<S: Into<String>, T>(message: S) -> ParseResult<T> {
|
||||
Err(ParseError::new(message))
|
||||
@ -19,8 +19,7 @@ pub mod prelude {
|
||||
}
|
||||
|
||||
pub use align::AlignFunc;
|
||||
pub use styles::{ItalicFunc, BoldFunc, MonospaceFunc};
|
||||
|
||||
pub use styles::{BoldFunc, ItalicFunc, MonospaceFunc};
|
||||
|
||||
/// Create a scope with all standard functions.
|
||||
pub fn std() -> Scope {
|
||||
|
@ -2,7 +2,6 @@ use toddle::query::FontClass;
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
|
||||
macro_rules! style_func {
|
||||
(
|
||||
$(#[$outer:meta])*
|
||||
|
@ -5,14 +5,13 @@ use std::collections::HashMap;
|
||||
use unicode_xid::UnicodeXID;
|
||||
|
||||
use crate::func::{Function, Scope};
|
||||
use crate::syntax::*;
|
||||
use crate::size::Size;
|
||||
use crate::syntax::*;
|
||||
|
||||
mod tokens;
|
||||
|
||||
pub use tokens::{tokenize, Tokens};
|
||||
|
||||
|
||||
/// Parses source code into a syntax tree given a context.
|
||||
#[inline]
|
||||
pub fn parse(src: &str, ctx: ParseContext) -> ParseResult<SyntaxTree> {
|
||||
@ -105,10 +104,7 @@ impl<'s> Parser<'s> {
|
||||
let body = self.parse_func_body(&header)?;
|
||||
|
||||
// Finally this function is parsed to the end.
|
||||
self.append(Node::Func(FuncCall {
|
||||
header,
|
||||
body,
|
||||
}));
|
||||
self.append(Node::Func(FuncCall { header, body }));
|
||||
|
||||
Ok(self.switch(ParserState::Body))
|
||||
}
|
||||
@ -124,7 +120,7 @@ impl<'s> Parser<'s> {
|
||||
} else {
|
||||
Err(ParseError::new(format!("invalid identifier: '{}'", word)))
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => Err(ParseError::new("expected identifier")),
|
||||
}?;
|
||||
|
||||
@ -138,13 +134,17 @@ impl<'s> Parser<'s> {
|
||||
|
||||
// Check for arguments
|
||||
match self.tokens.next() {
|
||||
Some(Token::RightBracket) => {},
|
||||
Some(Token::RightBracket) => {}
|
||||
Some(Token::Colon) => {
|
||||
let (args, kwargs) = self.parse_func_args()?;
|
||||
header.args = args;
|
||||
header.kwargs = kwargs;
|
||||
},
|
||||
_ => return Err(ParseError::new("expected function arguments or closing bracket")),
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError::new(
|
||||
"expected function arguments or closing bracket",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Store the header information of the function invocation.
|
||||
@ -164,16 +164,16 @@ impl<'s> Parser<'s> {
|
||||
Some(Token::Text(_)) | Some(Token::Quoted(_)) if !comma => {
|
||||
args.push(self.parse_expression()?);
|
||||
comma = true;
|
||||
},
|
||||
}
|
||||
|
||||
Some(Token::Comma) if comma => {
|
||||
self.advance();
|
||||
comma = false
|
||||
},
|
||||
}
|
||||
Some(Token::RightBracket) => {
|
||||
self.advance();
|
||||
break
|
||||
},
|
||||
break;
|
||||
}
|
||||
|
||||
_ if comma => return Err(ParseError::new("expected comma or closing bracket")),
|
||||
_ => return Err(ParseError::new("expected closing bracket")),
|
||||
@ -197,7 +197,7 @@ impl<'s> Parser<'s> {
|
||||
} else {
|
||||
Expression::Ident(text.to_owned())
|
||||
}
|
||||
},
|
||||
}
|
||||
_ => return Err(ParseError::new("expected expression")),
|
||||
})
|
||||
}
|
||||
@ -211,19 +211,25 @@ impl<'s> Parser<'s> {
|
||||
}
|
||||
|
||||
// Now we want to parse this function dynamically.
|
||||
let parser = self.ctx.scope.get_parser(&header.name)
|
||||
let parser = self
|
||||
.ctx
|
||||
.scope
|
||||
.get_parser(&header.name)
|
||||
.ok_or_else(|| ParseError::new(format!("unknown function: '{}'", &header.name)))?;
|
||||
|
||||
// Do the parsing dependent on whether the function has a body.
|
||||
Ok(if has_body {
|
||||
// Find out the string which makes the body of this function.
|
||||
let (start, end) = self.tokens.current_index().and_then(|index| {
|
||||
find_closing_bracket(&self.src[index..])
|
||||
.map(|end| (index, index + end))
|
||||
}).ok_or_else(|| ParseError::new("expected closing bracket"))?;
|
||||
let (start, end) = self
|
||||
.tokens
|
||||
.current_index()
|
||||
.and_then(|index| {
|
||||
find_closing_bracket(&self.src[index..]).map(|end| (index, index + end))
|
||||
})
|
||||
.ok_or_else(|| ParseError::new("expected closing bracket"))?;
|
||||
|
||||
// Parse the body.
|
||||
let body_string = &self.src[start .. end];
|
||||
let body_string = &self.src[start..end];
|
||||
let body = parser(&header, Some(body_string), self.ctx)?;
|
||||
|
||||
// Skip to the end of the function in the token stream.
|
||||
@ -246,12 +252,12 @@ impl<'s> Parser<'s> {
|
||||
Token::Newline => {
|
||||
self.append_consumed(Node::Newline);
|
||||
self.switch(ParserState::WroteNewline);
|
||||
},
|
||||
}
|
||||
Token::Space => self.append_space_consumed(),
|
||||
_ => {
|
||||
self.append_space();
|
||||
self.switch(ParserState::Body);
|
||||
},
|
||||
}
|
||||
},
|
||||
ParserState::WroteNewline => match token {
|
||||
Token::Newline | Token::Space => self.append_space_consumed(),
|
||||
@ -263,17 +269,17 @@ impl<'s> Parser<'s> {
|
||||
Token::Newline => {
|
||||
self.advance();
|
||||
self.switch(ParserState::FirstNewline);
|
||||
},
|
||||
}
|
||||
|
||||
// Comments
|
||||
Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
|
||||
Token::StarSlash => {
|
||||
return Err(ParseError::new("unexpected end of block comment"));
|
||||
},
|
||||
}
|
||||
|
||||
// Anything else skips out of the function.
|
||||
_ => break,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,8 +290,9 @@ impl<'s> Parser<'s> {
|
||||
fn skip_white(&mut self) {
|
||||
while let Some(token) = self.tokens.peek() {
|
||||
match token {
|
||||
Token::Space | Token::Newline
|
||||
| Token::LineComment(_) | Token::BlockComment(_) => self.advance(),
|
||||
Token::Space | Token::Newline | Token::LineComment(_) | Token::BlockComment(_) => {
|
||||
self.advance()
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
@ -335,19 +342,19 @@ fn find_closing_bracket(src: &str) -> Option<usize> {
|
||||
'\\' => {
|
||||
escaped = !escaped;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
']' if !escaped && parens == 0 => return Some(index),
|
||||
'[' if !escaped => parens += 1,
|
||||
']' if !escaped => parens -= 1,
|
||||
_ => {},
|
||||
_ => {}
|
||||
}
|
||||
escaped = false;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// A peekable iterator for tokens which allows access to the original iterator inside this module
|
||||
/// (which is needed by the parser).
|
||||
/// A peekable iterator for tokens which allows access to the original iterator
|
||||
/// inside this module (which is needed by the parser).
|
||||
#[derive(Debug, Clone)]
|
||||
struct PeekableTokens<'s> {
|
||||
tokens: Tokens<'s>,
|
||||
@ -411,7 +418,6 @@ fn is_identifier(string: &str) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
/// The error type for parsing.
|
||||
pub struct ParseError(String);
|
||||
|
||||
@ -430,17 +436,16 @@ error_type! {
|
||||
show: f => f.write_str(&err.0),
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::func::{Function, FuncCommands, Scope};
|
||||
use crate::func::{FuncCommands, Function, Scope};
|
||||
use crate::layout::{LayoutContext, LayoutResult};
|
||||
use Node::{Space as S, Newline as N, Func as F};
|
||||
use funcs::*;
|
||||
use Node::{Func as F, Newline as N, Space as S};
|
||||
|
||||
/// Two test functions, one which parses it's body as another syntax tree and another one which
|
||||
/// does not expect a body.
|
||||
/// Two test functions, one which parses it's body as another syntax tree
|
||||
/// and another one which does not expect a body.
|
||||
mod funcs {
|
||||
use super::*;
|
||||
|
||||
@ -449,8 +454,8 @@ mod tests {
|
||||
pub struct TreeFn(pub SyntaxTree);
|
||||
|
||||
impl Function for TreeFn {
|
||||
fn parse(_: &FuncHeader, body: Option<&str>, ctx: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized {
|
||||
fn parse(_: &FuncHeader, body: Option<&str>, ctx: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if let Some(src) = body {
|
||||
parse(src, ctx).map(|tree| TreeFn(tree))
|
||||
} else {
|
||||
@ -468,8 +473,8 @@ mod tests {
|
||||
pub struct BodylessFn;
|
||||
|
||||
impl Function for BodylessFn {
|
||||
fn parse(_: &FuncHeader, body: Option<&str>, _: ParseContext)
|
||||
-> ParseResult<Self> where Self: Sized {
|
||||
fn parse(_: &FuncHeader, body: Option<&str>, _: ParseContext) -> ParseResult<Self>
|
||||
where Self: Sized {
|
||||
if body.is_none() {
|
||||
Ok(BodylessFn)
|
||||
} else {
|
||||
@ -485,7 +490,9 @@ mod tests {
|
||||
|
||||
/// Test if the source code parses into the syntax tree.
|
||||
fn test(src: &str, tree: SyntaxTree) {
|
||||
let ctx = ParseContext { scope: &Scope::new() };
|
||||
let ctx = ParseContext {
|
||||
scope: &Scope::new(),
|
||||
};
|
||||
assert_eq!(parse(src, ctx).unwrap(), tree);
|
||||
}
|
||||
|
||||
@ -497,7 +504,9 @@ mod tests {
|
||||
|
||||
/// Test if the source parses into the error.
|
||||
fn test_err(src: &str, err: &str) {
|
||||
let ctx = ParseContext { scope: &Scope::new() };
|
||||
let ctx = ParseContext {
|
||||
scope: &Scope::new(),
|
||||
};
|
||||
assert_eq!(parse(src, ctx).unwrap_err().to_string(), err);
|
||||
}
|
||||
|
||||
@ -509,9 +518,12 @@ mod tests {
|
||||
|
||||
/// Create a text node.
|
||||
#[allow(non_snake_case)]
|
||||
fn T(s: &str) -> Node { Node::Text(s.to_owned()) }
|
||||
fn T(s: &str) -> Node {
|
||||
Node::Text(s.to_owned())
|
||||
}
|
||||
|
||||
/// Shortcut macro to create a syntax tree. Is `vec`-like and the elements are the nodes.
|
||||
/// Shortcut macro to create a syntax tree. Is `vec`-like and the elements
|
||||
/// are the nodes.
|
||||
macro_rules! tree {
|
||||
($($x:expr),*) => (
|
||||
SyntaxTree { nodes: vec![$($x),*] }
|
||||
|
@ -4,7 +4,6 @@ use smallvec::SmallVec;
|
||||
|
||||
use crate::syntax::*;
|
||||
|
||||
|
||||
/// Builds an iterator over the tokens of the source code.
|
||||
#[inline]
|
||||
pub fn tokenize(src: &str) -> Tokens {
|
||||
@ -15,7 +14,7 @@ pub fn tokenize(src: &str) -> Tokens {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Tokens<'s> {
|
||||
src: &'s str,
|
||||
pub(in super) chars: PeekableChars<'s>,
|
||||
pub(super) chars: PeekableChars<'s>,
|
||||
state: TokensState,
|
||||
stack: SmallVec<[TokensState; 1]>,
|
||||
}
|
||||
@ -56,7 +55,7 @@ impl<'s> Tokens<'s> {
|
||||
|
||||
/// Go back to the top-of-stack state.
|
||||
fn unswitch(&mut self) {
|
||||
self.state = self.stack.pop().unwrap_or(TokensState::Body);
|
||||
self.state = self.stack.pop().unwrap_or(TokensState::Body);
|
||||
}
|
||||
|
||||
/// Advance and return the given token.
|
||||
@ -67,7 +66,7 @@ impl<'s> Tokens<'s> {
|
||||
|
||||
/// Returns a word containing the string bounded by the given indices.
|
||||
fn text(&self, start: usize, end: usize) -> Token<'s> {
|
||||
Token::Text(&self.src[start .. end])
|
||||
Token::Text(&self.src[start..end])
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,7 +77,8 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
fn next(&mut self) -> Option<Token<'s>> {
|
||||
use TokensState as TU;
|
||||
|
||||
// Go to the body state if the function has a body or return to the top-of-stack state.
|
||||
// Go to the body state if the function has a body or return to the top-of-stack
|
||||
// state.
|
||||
if self.state == TU::MaybeBody {
|
||||
if self.chars.peek()?.1 == '[' {
|
||||
self.state = TU::Body;
|
||||
@ -97,7 +97,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
'[' => {
|
||||
self.switch(TU::Function);
|
||||
Token::LeftBracket
|
||||
},
|
||||
}
|
||||
']' => {
|
||||
if self.state == TU::Function {
|
||||
self.state = TU::MaybeBody;
|
||||
@ -105,7 +105,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
self.unswitch();
|
||||
}
|
||||
Token::RightBracket
|
||||
},
|
||||
}
|
||||
|
||||
// Line comment
|
||||
'/' if afterwards == Some('/') => {
|
||||
@ -121,8 +121,8 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
}
|
||||
|
||||
let end = end.0 + end.1.len_utf8();
|
||||
Token::LineComment(&self.src[start .. end])
|
||||
},
|
||||
Token::LineComment(&self.src[start..end])
|
||||
}
|
||||
|
||||
// Block comment
|
||||
'/' if afterwards == Some('*') => {
|
||||
@ -133,17 +133,26 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
while let Some((index, c)) = self.chars.next() {
|
||||
let after = self.chars.peek().map(|p| p.1);
|
||||
match (c, after) {
|
||||
('*', Some('/')) if nested == 0 => { self.advance(); break },
|
||||
('/', Some('*')) => { self.advance(); nested += 1 },
|
||||
('*', Some('/')) => { self.advance(); nested -= 1 },
|
||||
_ => {},
|
||||
('*', Some('/')) if nested == 0 => {
|
||||
self.advance();
|
||||
break;
|
||||
}
|
||||
('/', Some('*')) => {
|
||||
self.advance();
|
||||
nested += 1
|
||||
}
|
||||
('*', Some('/')) => {
|
||||
self.advance();
|
||||
nested -= 1
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
end = (index, c);
|
||||
}
|
||||
|
||||
let end = end.0 + end.1.len_utf8();
|
||||
Token::BlockComment(&self.src[start .. end])
|
||||
},
|
||||
Token::BlockComment(&self.src[start..end])
|
||||
}
|
||||
|
||||
// Unexpected end of block comment
|
||||
'*' if afterwards == Some('/') => self.consumed(Token::StarSlash),
|
||||
@ -189,7 +198,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
}
|
||||
|
||||
let end_pos = end.0 + end.1.len_utf8();
|
||||
Token::Quoted(&self.src[next_pos + 1 .. end_pos])
|
||||
Token::Quoted(&self.src[next_pos + 1..end_pos])
|
||||
}
|
||||
|
||||
// Escaping
|
||||
@ -207,7 +216,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
}
|
||||
|
||||
Token::Text("\\")
|
||||
},
|
||||
}
|
||||
|
||||
// Normal text
|
||||
_ => {
|
||||
@ -241,7 +250,7 @@ impl<'s> Iterator for Tokens<'s> {
|
||||
|
||||
let end_pos = end.0 + end.1.len_utf8();
|
||||
self.text(next_pos, end_pos)
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -328,20 +337,20 @@ impl Iterator for PeekableChars<'_> {
|
||||
Some(value) => {
|
||||
self.peek1 = self.peek2.take();
|
||||
value
|
||||
},
|
||||
}
|
||||
None => self.next_inner(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use Token::{Space as S, Newline as N, LeftBracket as L, RightBracket as R,
|
||||
Colon as C, Equals as E, Quoted as Q, Underscore as TU, Star as TS,
|
||||
Backtick as TB, Text as T, LineComment as LC, BlockComment as BC,
|
||||
StarSlash as SS};
|
||||
use Token::{
|
||||
Backtick as TB, BlockComment as BC, Colon as C, Equals as E, LeftBracket as L,
|
||||
LineComment as LC, Newline as N, Quoted as Q, RightBracket as R, Space as S, Star as TS,
|
||||
StarSlash as SS, Text as T, Underscore as TU,
|
||||
};
|
||||
|
||||
/// Test if the source code tokenizes to the tokens.
|
||||
fn test(src: &str, tokens: Vec<Token>) {
|
||||
|
114
src/size.rs
114
src/size.rs
@ -6,7 +6,6 @@ use std::iter::Sum;
|
||||
use std::ops::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
|
||||
/// A general spacing type.
|
||||
#[derive(Copy, Clone, PartialEq, Default)]
|
||||
pub struct Size {
|
||||
@ -39,57 +38,89 @@ pub struct SizeBox {
|
||||
impl Size {
|
||||
/// Create a zeroed size.
|
||||
#[inline]
|
||||
pub fn zero() -> Size { Size::default() }
|
||||
pub fn zero() -> Size {
|
||||
Size::default()
|
||||
}
|
||||
|
||||
/// Create a size from an amount of points.
|
||||
#[inline]
|
||||
pub fn pt(points: f32) -> Size { Size { points } }
|
||||
pub fn pt(points: f32) -> Size {
|
||||
Size { points }
|
||||
}
|
||||
|
||||
/// Create a size from an amount of millimeters.
|
||||
#[inline]
|
||||
pub fn mm(mm: f32) -> Size { Size { points: 2.83465 * mm } }
|
||||
pub fn mm(mm: f32) -> Size {
|
||||
Size {
|
||||
points: 2.83465 * mm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a size from an amount of centimeters.
|
||||
#[inline]
|
||||
pub fn cm(cm: f32) -> Size { Size { points: 28.3465 * cm } }
|
||||
pub fn cm(cm: f32) -> Size {
|
||||
Size {
|
||||
points: 28.3465 * cm,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a size from an amount of inches.
|
||||
#[inline]
|
||||
pub fn inches(inches: f32) -> Size { Size { points: 72.0 * inches } }
|
||||
pub fn inches(inches: f32) -> Size {
|
||||
Size {
|
||||
points: 72.0 * inches,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this size into points.
|
||||
#[inline]
|
||||
pub fn to_pt(&self) -> f32 { self.points }
|
||||
pub fn to_pt(&self) -> f32 {
|
||||
self.points
|
||||
}
|
||||
|
||||
/// Convert this size into millimeters.
|
||||
#[inline]
|
||||
pub fn to_mm(&self) -> f32 { self.points * 0.352778 }
|
||||
pub fn to_mm(&self) -> f32 {
|
||||
self.points * 0.352778
|
||||
}
|
||||
|
||||
/// Convert this size into centimeters.
|
||||
#[inline]
|
||||
pub fn to_cm(&self) -> f32 { self.points * 0.0352778 }
|
||||
pub fn to_cm(&self) -> f32 {
|
||||
self.points * 0.0352778
|
||||
}
|
||||
|
||||
/// Convert this size into inches.
|
||||
#[inline]
|
||||
pub fn to_inches(&self) -> f32 { self.points * 0.0138889 }
|
||||
pub fn to_inches(&self) -> f32 {
|
||||
self.points * 0.0138889
|
||||
}
|
||||
}
|
||||
|
||||
impl Size2D {
|
||||
/// Create a new vector from two sizes.
|
||||
#[inline]
|
||||
pub fn new(x: Size, y: Size) -> Size2D { Size2D { x, y } }
|
||||
pub fn new(x: Size, y: Size) -> Size2D {
|
||||
Size2D { x, y }
|
||||
}
|
||||
|
||||
/// Create a vector with all set to zero.
|
||||
#[inline]
|
||||
pub fn zero() -> Size2D { Size2D::default() }
|
||||
pub fn zero() -> Size2D {
|
||||
Size2D::default()
|
||||
}
|
||||
|
||||
/// Create a new vector with `y` set to zero and `x` to a value.
|
||||
#[inline]
|
||||
pub fn with_x(x: Size) -> Size2D { Size2D { x, y: Size::zero() } }
|
||||
pub fn with_x(x: Size) -> Size2D {
|
||||
Size2D { x, y: Size::zero() }
|
||||
}
|
||||
|
||||
/// Create a new vector with `x` set to zero and `y` to a value.
|
||||
#[inline]
|
||||
pub fn with_y(y: Size) -> Size2D { Size2D { x: Size::zero(), y } }
|
||||
pub fn with_y(y: Size) -> Size2D {
|
||||
Size2D { x: Size::zero(), y }
|
||||
}
|
||||
|
||||
/// Return a [`Size2D`] padded by the paddings of the given box.
|
||||
#[inline]
|
||||
@ -105,7 +136,12 @@ impl SizeBox {
|
||||
/// Create a new box from four sizes.
|
||||
#[inline]
|
||||
pub fn new(left: Size, top: Size, right: Size, bottom: Size) -> SizeBox {
|
||||
SizeBox { left, top, right, bottom }
|
||||
SizeBox {
|
||||
left,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a box with all set to zero.
|
||||
@ -117,12 +153,20 @@ impl SizeBox {
|
||||
|
||||
/// The maximum of two sizes.
|
||||
pub fn max(a: Size, b: Size) -> Size {
|
||||
if a >= b { a } else { b }
|
||||
if a >= b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum of two sizes.
|
||||
pub fn min(a: Size, b: Size) -> Size {
|
||||
if a <= b { a } else { b }
|
||||
if a <= b {
|
||||
a
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------//
|
||||
@ -146,10 +190,11 @@ impl FromStr for Size {
|
||||
return Err(ParseSizeError);
|
||||
}
|
||||
|
||||
let value = src[.. src.len() - 2].parse::<f32>()
|
||||
let value = src[..src.len() - 2]
|
||||
.parse::<f32>()
|
||||
.map_err(|_| ParseSizeError)?;
|
||||
|
||||
Ok(match &src[src.len() - 2 ..] {
|
||||
Ok(match &src[src.len() - 2..] {
|
||||
"pt" => Size::pt(value),
|
||||
"mm" => Size::mm(value),
|
||||
"cm" => Size::cm(value),
|
||||
@ -171,13 +216,16 @@ impl Neg for Size {
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Size {
|
||||
Size { points: -self.points }
|
||||
Size {
|
||||
points: -self.points,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum for Size {
|
||||
#[inline]
|
||||
fn sum<I>(iter: I) -> Size where I: Iterator<Item=Size> {
|
||||
fn sum<I>(iter: I) -> Size
|
||||
where I: Iterator<Item = Size> {
|
||||
iter.fold(Size::zero(), Add::add)
|
||||
}
|
||||
}
|
||||
@ -189,7 +237,9 @@ macro_rules! impl_reflexive {
|
||||
|
||||
#[inline]
|
||||
fn $func(self, other: Size) -> Size {
|
||||
Size { points: $trait::$func(self.points, other.points) }
|
||||
Size {
|
||||
points: $trait::$func(self.points, other.points),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,7 +259,9 @@ macro_rules! impl_num_back {
|
||||
|
||||
#[inline]
|
||||
fn $func(self, other: $ty) -> Size {
|
||||
Size { points: $trait::$func(self.points, other as f32) }
|
||||
Size {
|
||||
points: $trait::$func(self.points, other as f32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +283,9 @@ macro_rules! impl_num_both {
|
||||
|
||||
#[inline]
|
||||
fn $func(self, other: Size) -> Size {
|
||||
Size { points: $trait::$func(self as f32, other.points) }
|
||||
Size {
|
||||
points: $trait::$func(self as f32, other.points),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -257,7 +311,10 @@ impl Neg for Size2D {
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Size2D {
|
||||
Size2D { x: -self.x, y: -self.y }
|
||||
Size2D {
|
||||
x: -self.x,
|
||||
y: -self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,8 +393,11 @@ impl_num_back2d!(Div, div, DivAssign, div_assign, i32);
|
||||
|
||||
impl Display for SizeBox {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "[left: {}, top: {}, right: {}, bottom: {}]",
|
||||
self.left, self.top, self.right, self.bottom)
|
||||
write!(
|
||||
f,
|
||||
"[left: {}, top: {}, right: {}, bottom: {}]",
|
||||
self.left, self.top, self.right, self.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
12
src/style.rs
12
src/style.rs
@ -4,14 +4,13 @@ use toddle::query::FontClass;
|
||||
|
||||
use crate::size::{Size, Size2D, SizeBox};
|
||||
|
||||
|
||||
/// Default styles for text.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TextStyle {
|
||||
/// The classes the font we want has to be part of.
|
||||
pub classes: Vec<FontClass>,
|
||||
/// A sequence of classes. We need the font to be part of at least one of these
|
||||
/// and preferably the leftmost possible.
|
||||
/// A sequence of classes. We need the font to be part of at least one of
|
||||
/// these and preferably the leftmost possible.
|
||||
pub fallback: Vec<FontClass>,
|
||||
/// The font size.
|
||||
pub font_size: f32,
|
||||
@ -26,13 +25,14 @@ impl TextStyle {
|
||||
///
|
||||
/// If the class was one of _italic_ or _bold_, then:
|
||||
/// - If it was not present, the _regular_ class will be removed.
|
||||
/// - If it was present, the _regular_ class will be added in case the
|
||||
/// other style class is not present.
|
||||
/// - If it was present, the _regular_ class will be added in case the other
|
||||
/// style class is not present.
|
||||
pub fn toggle_class(&mut self, class: FontClass) {
|
||||
if self.classes.contains(&class) {
|
||||
self.classes.retain(|x| x != &class);
|
||||
if (class == FontClass::Italic && !self.classes.contains(&FontClass::Bold))
|
||||
|| (class == FontClass::Bold && !self.classes.contains(&FontClass::Italic)) {
|
||||
|| (class == FontClass::Bold && !self.classes.contains(&FontClass::Italic))
|
||||
{
|
||||
self.classes.push(FontClass::Regular);
|
||||
}
|
||||
} else {
|
||||
|
@ -6,24 +6,26 @@ use std::fmt::{self, Display, Formatter};
|
||||
use crate::func::Function;
|
||||
use crate::size::Size;
|
||||
|
||||
|
||||
/// A logical unit of the incoming text stream.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
pub enum Token<'s> {
|
||||
/// One or more whitespace (non-newline) codepoints.
|
||||
Space,
|
||||
/// A line feed (`\n`, `\r\n` and some more as defined by the Unicode standard).
|
||||
/// A line feed (`\n`, `\r\n` and some more as defined by the Unicode
|
||||
/// standard).
|
||||
Newline,
|
||||
/// A left bracket: `[`.
|
||||
LeftBracket,
|
||||
/// A right bracket: `]`.
|
||||
RightBracket,
|
||||
/// A colon (`:`) indicating the beginning of function arguments (Function header only).
|
||||
/// A colon (`:`) indicating the beginning of function arguments (Function
|
||||
/// header only).
|
||||
///
|
||||
/// If a colon occurs outside of a function header, it will be tokenized as a
|
||||
/// [Word](Token::Word).
|
||||
/// If a colon occurs outside of a function header, it will be tokenized as
|
||||
/// a [Word](Token::Word).
|
||||
Colon,
|
||||
/// An equals (`=`) sign assigning a function argument a value (Function header only).
|
||||
/// An equals (`=`) sign assigning a function argument a value (Function
|
||||
/// header only).
|
||||
Equals,
|
||||
/// A comma (`,`) separating two function arguments (Function header only).
|
||||
Comma,
|
||||
@ -39,8 +41,9 @@ pub enum Token<'s> {
|
||||
LineComment(&'s str),
|
||||
/// A block comment.
|
||||
BlockComment(&'s str),
|
||||
/// A star followed by a slash unexpectedly ending a block comment (the comment was not started
|
||||
/// before, otherwise a [BlockComment](Token::BlockComment) would be returned).
|
||||
/// A star followed by a slash unexpectedly ending a block comment (the
|
||||
/// comment was not started before, otherwise a
|
||||
/// [BlockComment](Token::BlockComment) would be returned).
|
||||
StarSlash,
|
||||
/// Everything else is just text.
|
||||
Text(&'s str),
|
||||
@ -98,7 +101,7 @@ impl PartialEq for FuncCall {
|
||||
pub struct FuncHeader {
|
||||
pub name: String,
|
||||
pub args: Vec<Expression>,
|
||||
pub kwargs: HashMap<String, Expression>
|
||||
pub kwargs: HashMap<String, Expression>,
|
||||
}
|
||||
|
||||
/// A value expression.
|
||||
|
@ -1,15 +1,14 @@
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Write, Read, BufWriter};
|
||||
use std::io::{BufWriter, Read, Write};
|
||||
use std::process::Command;
|
||||
|
||||
use typst::Typesetter;
|
||||
use typst::export::pdf::PdfExporter;
|
||||
use typst::layout::LayoutAction;
|
||||
use typst::toddle::query::FileSystemFontProvider;
|
||||
use typst::export::pdf::PdfExporter;
|
||||
use typst::Typesetter;
|
||||
|
||||
const CACHE_DIR: &str = "test-cache";
|
||||
|
||||
|
||||
fn main() {
|
||||
let mut perfect_match = false;
|
||||
let mut filter = Vec::new();
|
||||
@ -31,9 +30,7 @@ fn main() {
|
||||
for entry in fs::read_dir("tests/layouts/").unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
|
||||
let name = path
|
||||
.file_stem().unwrap()
|
||||
.to_str().unwrap();
|
||||
let name = path.file_stem().unwrap().to_str().unwrap();
|
||||
|
||||
let matches = if perfect_match {
|
||||
filter.iter().any(|pattern| name == pattern)
|
||||
|
Loading…
x
Reference in New Issue
Block a user