summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2020-01-06 12:41:42 +0100
committerLaurenz <laurmaedje@gmail.com>2020-01-06 12:41:42 +0100
commit2ee5810fecb96a8d4e0d078faecc8c91096d6881 (patch)
tree702c746a3021f5034e1b31cd07e8fadba0e4dd7a
parentbd384a2a633e21cd7deff7ed2a29a9c03a63a20e (diff)
Asyncify font loading 🪐
-rw-r--r--Cargo.toml10
-rw-r--r--src/bin/main.rs4
-rw-r--r--src/func/macros.rs17
-rw-r--r--src/func/mod.rs5
-rw-r--r--src/layout/text.rs18
-rw-r--r--src/layout/tree.rs179
-rw-r--r--src/lib.rs8
-rw-r--r--src/library/align.rs2
-rw-r--r--src/library/boxed.rs2
-rw-r--r--src/library/direction.rs2
-rw-r--r--src/library/mod.rs12
-rw-r--r--src/style.rs11
-rw-r--r--src/syntax/tokens.rs5
-rw-r--r--tests/layout.rs8
14 files changed, 160 insertions, 123 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 76db3d18..2082d916 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,20 +6,28 @@ edition = "2018"
build = "build.rs"
[dependencies]
+toddle = { path = "../toddle", default-features = false }
tide = { path = "../tide" }
-toddle = { path = "../toddle" }
byteorder = "1"
smallvec = "0.6.10"
unicode-xid = "0.1.0"
+async-trait = "0.1.22"
+futures-executor = { version = "0.3", optional = true }
+
+[features]
+default = ["fs-provider", "futures-executor"]
+fs-provider = ["toddle/fs-provider"]
[[bin]]
name = "typst-bin"
path = "src/bin/main.rs"
+required-features = ["futures-executor"]
[[test]]
name = "layout"
path = "tests/layout.rs"
harness = false
+required-features = ["futures-executor"]
[[test]]
name = "parse"
diff --git a/src/bin/main.rs b/src/bin/main.rs
index e0bcd16d..f86336bb 100644
--- a/src/bin/main.rs
+++ b/src/bin/main.rs
@@ -2,6 +2,8 @@ use std::fs::{File, read_to_string};
use std::io::BufWriter;
use std::path::{Path, PathBuf};
+use futures_executor::block_on;
+
use typstc::Typesetter;
use typstc::toddle::query::FileSystemFontProvider;
use typstc::export::pdf::PdfExporter;
@@ -39,7 +41,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
let provider = FileSystemFontProvider::from_index("../fonts/index.json").unwrap();
typesetter.add_font_provider(provider);
- let layouts = typesetter.typeset(&src)?;
+ let layouts = block_on(typesetter.typeset(&src))?;
let exporter = PdfExporter::new();
let writer = BufWriter::new(File::create(&dest)?);
diff --git a/src/func/macros.rs b/src/func/macros.rs
index 2da219bc..a89156b7 100644
--- a/src/func/macros.rs
+++ b/src/func/macros.rs
@@ -108,9 +108,20 @@ macro_rules! function {
// (2-arg) Parse a layout-definition with all arguments.
(@layout $type:ident | layout($this:ident, $ctx:pat) $code:block) => {
- impl $crate::func::LayoutFunc for $type {
- fn layout(&$this, $ctx: LayoutContext) -> LayoutResult<Commands> {
- Ok($code)
+ impl LayoutFunc for $type {
+ fn layout<'a, 'life0, 'life1, 'async_trait>(
+ &'a $this,
+ $ctx: LayoutContext<'life0, 'life1>
+ ) -> std::pin::Pin<Box<
+ dyn std::future::Future<Output = LayoutResult<Commands<'a>>> + 'async_trait
+ >>
+ where
+ 'a: 'async_trait,
+ 'life0: 'async_trait,
+ 'life1: 'async_trait,
+ Self: 'async_trait,
+ {
+ Box::pin(async move { Ok($code) })
}
}
};
diff --git a/src/func/mod.rs b/src/func/mod.rs
index 427e5b6f..a0875cf9 100644
--- a/src/func/mod.rs
+++ b/src/func/mod.rs
@@ -4,6 +4,7 @@ use std::any::Any;
use std::collections::HashMap;
use std::fmt::{self, Debug, Formatter};
+use async_trait::async_trait;
use self::prelude::*;
#[macro_use]
@@ -24,6 +25,7 @@ pub mod prelude {
pub use Command::*;
}
+
/// Types representing functions that are parsed from source code.
pub trait ParseFunc {
type Meta: Clone;
@@ -43,12 +45,13 @@ pub trait ParseFunc {
/// The trait `[LayoutFuncBounds]` is automatically implemented for types which
/// can be used as functions, that is, all types which fulfill the bounds `Debug
/// + PartialEq + 'static`.
+#[async_trait(?Send)]
pub trait LayoutFunc: LayoutFuncBounds {
/// Layout this function in a given context.
///
/// Returns a sequence of layouting commands which describe what the
/// function is doing.
- fn layout(&self, ctx: LayoutContext) -> LayoutResult<Commands>;
+ async fn layout<'a>(&'a self, ctx: LayoutContext<'_, '_>) -> LayoutResult<Commands<'a>>;
}
impl dyn LayoutFunc {
diff --git a/src/layout/text.rs b/src/layout/text.rs
index 96704f60..2fdb3f6d 100644
--- a/src/layout/text.rs
+++ b/src/layout/text.rs
@@ -20,8 +20,8 @@ pub struct TextContext<'a, 'p> {
///
/// There is no complex layout involved. The text is simply laid out left-
/// to-right using the correct font for each character.
-pub fn layout_text(text: &str, ctx: TextContext) -> LayoutResult<Layout> {
- TextLayouter::new(text, ctx).layout()
+pub async fn layout_text(text: &str, ctx: TextContext<'_, '_>) -> LayoutResult<Layout> {
+ TextLayouter::new(text, ctx).layout().await
}
/// Layouts text into boxes.
@@ -48,14 +48,14 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
/// Layout the text
- fn layout(mut self) -> LayoutResult<Layout> {
+ async fn layout(mut self) -> LayoutResult<Layout> {
if self.ctx.axes.primary.is_positive() {
for c in self.text.chars() {
- self.layout_char(c)?;
+ self.layout_char(c).await?;
}
} else {
for c in self.text.chars().rev() {
- self.layout_char(c)?;
+ self.layout_char(c).await?;
}
}
@@ -71,8 +71,8 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
}
/// Layout an individual character.
- fn layout_char(&mut self, c: char) -> LayoutResult<()> {
- let (index, char_width) = self.select_font(c)?;
+ async fn layout_char(&mut self, c: char) -> LayoutResult<()> {
+ let (index, char_width) = self.select_font(c).await?;
self.width += char_width;
@@ -93,7 +93,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
/// Select the best font for a character and return its index along with
/// the width of the char in the font.
- fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> {
+ async fn select_font(&mut self, c: char) -> LayoutResult<(FontIndex, Size)> {
let mut loader = self.ctx.loader.borrow_mut();
let query = FontQuery {
@@ -102,7 +102,7 @@ impl<'a, 'p> TextLayouter<'a, 'p> {
c,
};
- if let Some((font, index)) = loader.get(query) {
+ if let Some((font, index)) = loader.get(query).await {
let font_unit_ratio = 1.0 / (font.read_table::<Header>()?.units_per_em as f32);
let font_unit_to_size = |x| Size::pt(font_unit_ratio * x);
diff --git a/src/layout/tree.rs b/src/layout/tree.rs
index 4ed3d82a..f645a35d 100644
--- a/src/layout/tree.rs
+++ b/src/layout/tree.rs
@@ -1,3 +1,5 @@
+use std::pin::Pin;
+use std::future::Future;
use smallvec::smallvec;
use crate::func::Command;
@@ -5,10 +7,13 @@ use crate::syntax::{SyntaxTree, Node, FuncCall};
use crate::style::TextStyle;
use super::*;
+
+type RecursiveResult<'a, T> = Pin<Box<dyn Future<Output=LayoutResult<T>> + 'a>>;
+
/// Layout a syntax tree into a multibox.
-pub fn layout(tree: &SyntaxTree, ctx: LayoutContext) -> LayoutResult<MultiLayout> {
+pub async fn layout(tree: &SyntaxTree, ctx: LayoutContext<'_, '_>) -> LayoutResult<MultiLayout> {
let mut layouter = TreeLayouter::new(ctx);
- layouter.layout(tree)?;
+ layouter.layout(tree).await?;
layouter.finish()
}
@@ -36,42 +41,44 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
}
}
- fn layout(&mut self, tree: &SyntaxTree) -> LayoutResult<()> {
- for node in &tree.nodes {
- match &node.v {
- Node::Text(text) => self.layout_text(text)?,
+ fn layout<'b>(&'b mut self, tree: &'b SyntaxTree) -> RecursiveResult<'b, ()> {
+ Box::pin(async move {
+ for node in &tree.nodes {
+ match &node.v {
+ Node::Text(text) => self.layout_text(text).await?,
- Node::Space => self.layout_space(),
- Node::Newline => self.layout_paragraph()?,
+ Node::Space => self.layout_space(),
+ Node::Newline => self.layout_paragraph()?,
- Node::ToggleItalics => self.style.text.variant.style.toggle(),
- Node::ToggleBolder => {
- self.style.text.variant.weight.0 += 300 *
- if self.style.text.bolder { -1 } else { 1 };
- self.style.text.bolder = !self.style.text.bolder;
- }
- Node::ToggleMonospace => {
- let list = &mut self.style.text.fallback.list;
- match list.get(0).map(|s| s.as_str()) {
- Some("monospace") => { list.remove(0); },
- _ => list.insert(0, "monospace".to_string()),
+ Node::ToggleItalics => self.style.text.variant.style.toggle(),
+ Node::ToggleBolder => {
+ self.style.text.variant.weight.0 += 300 *
+ if self.style.text.bolder { -1 } else { 1 };
+ self.style.text.bolder = !self.style.text.bolder;
+ }
+ Node::ToggleMonospace => {
+ let list = &mut self.style.text.fallback.list;
+ match list.get(0).map(|s| s.as_str()) {
+ Some("monospace") => { list.remove(0); },
+ _ => list.insert(0, "monospace".to_string()),
+ }
}
- }
- Node::Func(func) => self.layout_func(func)?,
+ Node::Func(func) => self.layout_func(func).await?,
+ }
}
- }
- Ok(())
+ Ok(())
+ })
}
- fn layout_text(&mut self, text: &str) -> LayoutResult<()> {
+ async fn layout_text(&mut self, text: &str) -> LayoutResult<()> {
let layout = layout_text(text, TextContext {
loader: &self.ctx.loader,
style: &self.style.text,
axes: self.ctx.axes,
alignment: self.ctx.alignment,
- })?;
+ }).await?;
self.layouter.add(layout)
}
@@ -84,75 +91,71 @@ impl<'a, 'p> TreeLayouter<'a, 'p> {
self.layouter.add_secondary_spacing(self.style.text.paragraph_spacing(), PARAGRAPH_KIND)
}
- fn layout_func(&mut self, func: &FuncCall) -> LayoutResult<()> {
- let commands = func.0.layout(LayoutContext {
- style: &self.style,
- spaces: self.layouter.remaining(),
- nested: true,
- debug: false,
- .. self.ctx
- })?;
-
- for command in commands {
- self.execute(command)?;
- }
-
- Ok(())
- }
-
- fn execute(&mut self, command: Command) -> LayoutResult<()> {
- use Command::*;
-
- match command {
- LayoutTree(tree) => self.layout(tree)?,
-
- Add(layout) => self.layouter.add(layout)?,
- AddMultiple(layouts) => self.layouter.add_multiple(layouts)?,
- SpacingFunc(space, kind, axis) => match axis {
- Primary => self.layouter.add_primary_spacing(space, kind),
- Secondary => self.layouter.add_secondary_spacing(space, kind)?,
- }
-
- FinishLine => self.layouter.finish_line()?,
- FinishSpace => self.layouter.finish_space(true)?,
- BreakParagraph => self.layout_paragraph()?,
- BreakPage => {
- if self.ctx.nested {
- error!("page break cannot be issued from nested context");
- }
-
- self.layouter.finish_space(true)?
- }
+ fn layout_func<'b>(&'b mut self, func: &'b FuncCall) -> RecursiveResult<'b, ()> {
+ Box::pin(async move {
+ let commands = func.0.layout(LayoutContext {
+ style: &self.style,
+ spaces: self.layouter.remaining(),
+ nested: true,
+ debug: false,
+ .. self.ctx
+ }).await?;
+
+ for command in commands {
+ use Command::*;
+
+ match command {
+ LayoutTree(tree) => self.layout(tree).await?,
+
+ Add(layout) => self.layouter.add(layout)?,
+ AddMultiple(layouts) => self.layouter.add_multiple(layouts)?,
+ SpacingFunc(space, kind, axis) => match axis {
+ Primary => self.layouter.add_primary_spacing(space, kind),
+ Secondary => self.layouter.add_secondary_spacing(space, kind)?,
+ }
- SetTextStyle(style) => {
- self.layouter.set_line_spacing(style.line_spacing());
- self.style.text = style;
- }
- SetPageStyle(style) => {
- if self.ctx.nested {
- error!("page style cannot be altered in nested context");
- }
+ FinishLine => self.layouter.finish_line()?,
+ FinishSpace => self.layouter.finish_space(true)?,
+ BreakParagraph => self.layout_paragraph()?,
+ BreakPage => {
+ if self.ctx.nested {
+ error!("page break cannot be issued from nested context");
+ }
- self.style.page = style;
+ self.layouter.finish_space(true)?
+ }
- let margins = style.margins();
- self.ctx.base = style.dimensions.unpadded(margins);
- self.layouter.set_spaces(smallvec![
- LayoutSpace {
- dimensions: style.dimensions,
- padding: margins,
- expansion: LayoutExpansion::new(true, true),
+ SetTextStyle(style) => {
+ self.layouter.set_line_spacing(style.line_spacing());
+ self.style.text = style;
}
- ], true);
- }
- SetAlignment(alignment) => self.ctx.alignment = alignment,
- SetAxes(axes) => {
- self.layouter.set_axes(axes);
- self.ctx.axes = axes;
+ SetPageStyle(style) => {
+ if self.ctx.nested {
+ error!("page style cannot be altered in nested context");
+ }
+
+ self.style.page = style;
+
+ let margins = style.margins();
+ self.ctx.base = style.dimensions.unpadded(margins);
+ self.layouter.set_spaces(smallvec![
+ LayoutSpace {
+ dimensions: style.dimensions,
+ padding: margins,
+ expansion: LayoutExpansion::new(true, true),
+ }
+ ], true);
+ }
+ SetAlignment(alignment) => self.ctx.alignment = alignment,
+ SetAxes(axes) => {
+ self.layouter.set_axes(axes);
+ self.ctx.axes = axes;
+ }
+ }
}
- }
- Ok(())
+ Ok(())
+ })
}
fn finish(self) -> LayoutResult<MultiLayout> {
diff --git a/src/lib.rs b/src/lib.rs
index 516e2a9c..7975ff7d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -89,7 +89,7 @@ impl<'p> Typesetter<'p> {
}
/// Layout a syntax tree and return the produced layout.
- pub fn layout(&self, tree: &SyntaxTree) -> LayoutResult<MultiLayout> {
+ pub async fn layout(&self, tree: &SyntaxTree) -> LayoutResult<MultiLayout> {
use crate::layout::prelude::*;
let margins = self.style.page.margins();
Ok(layout(
@@ -109,13 +109,13 @@ impl<'p> Typesetter<'p> {
nested: false,
debug: false,
},
- )?)
+ ).await?)
}
/// Process source code directly into a layout.
- pub fn typeset(&self, src: &str) -> TypesetResult<MultiLayout> {
+ pub async fn typeset(&self, src: &str) -> TypesetResult<MultiLayout> {
let tree = self.parse(src)?;
- let layout = self.layout(&tree)?;
+ let layout = self.layout(&tree).await?;
Ok(layout)
}
}
diff --git a/src/library/align.rs b/src/library/align.rs
index 03d905cd..6114c3a3 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -28,7 +28,7 @@ function! {
}
match &self.body {
- Some(body) => vec![AddMultiple(layout(&body, ctx)?)],
+ Some(body) => vec![AddMultiple(layout(&body, ctx).await?)],
None => vec![SetAlignment(ctx.alignment)],
}
}
diff --git a/src/library/boxed.rs b/src/library/boxed.rs
index a4d059cb..da06a371 100644
--- a/src/library/boxed.rs
+++ b/src/library/boxed.rs
@@ -46,7 +46,7 @@ function! {
ctx.spaces = smallvec![space];
- match layout(&self.body, ctx) {
+ match layout(&self.body, ctx).await {
Ok(layouts) => return Ok(vec![AddMultiple(layouts)]),
Err(err) => error = Some(err),
}
diff --git a/src/library/direction.rs b/src/library/direction.rs
index a0992075..39ac2ccd 100644
--- a/src/library/direction.rs
+++ b/src/library/direction.rs
@@ -36,7 +36,7 @@ function! {
}
match &self.body {
- Some(body) => vec![AddMultiple(layout(&body, ctx)?)],
+ Some(body) => vec![AddMultiple(layout(&body, ctx).await?)],
None => vec![Command::SetAxes(ctx.axes)],
}
}
diff --git a/src/library/mod.rs b/src/library/mod.rs
index ac1ac338..513590eb 100644
--- a/src/library/mod.rs
+++ b/src/library/mod.rs
@@ -55,19 +55,25 @@ function! {
#[derive(Debug, PartialEq)]
pub struct FontFamilyFunc {
body: Option<SyntaxTree>,
- family: String,
+ list: Vec<String>,
}
parse(args, body, ctx, meta) {
FontFamilyFunc {
body: parse!(optional: body, ctx),
- family: args.get_pos::<String>()?,
+ list: {
+ args.pos().map(|arg| match arg.v {
+ Expression::Str(s) |
+ Expression::Ident(Ident(s)) => Ok(s.to_lowercase()),
+ _ => error!("expected identifier or string"),
+ }).collect::<LayoutResult<Vec<_>>>()?
+ }
}
}
layout(self, ctx) {
let mut style = ctx.style.text.clone();
- style.fallback.list = vec![self.family.clone()];
+ style.fallback.list = self.list.clone();
styled(&self.body, &ctx, style)
}
}
diff --git a/src/style.rs b/src/style.rs
index cbe4bf01..68c76ad1 100644
--- a/src/style.rs
+++ b/src/style.rs
@@ -58,7 +58,7 @@ impl TextStyle {
}
macro_rules! fallback {
- (($($f:expr),*), $($c:expr => ($($cf:expr),*)),*) => ({
+ (($($f:expr),*), $($c:expr => ($($cf:expr),*),)*) => ({
let mut fallback = FontFallbackTree::new(vec![$($f.to_string()),*]);
$(
fallback.set_class_list($c.to_string(), vec![$($cf.to_string()),*])
@@ -74,10 +74,11 @@ impl Default for TextStyle {
TextStyle {
fallback: fallback! {
("sans-serif"),
- "serif" => ("source serif pro", "noto serif", "noto emoji"),
- "sans-serif" => ("source sans pro", "noto sans", "noto emoji"),
- "monospace" => ("source code pro", "noto sans mono", "noto emoji"),
- "math" => ("latin modern math", "serif")
+ "serif" => ("source serif pro", "noto serif", "__base"),
+ "sans-serif" => ("source sans pro", "noto sans", "__base"),
+ "monospace" => ("source code pro", "noto sans mono", "__base"),
+ "math" => ("latin modern math", "serif", "__base"),
+ "__base" => ("latin modern math", "noto emoji"),
},
variant: FontVariant {
style: FontStyle::Normal,
diff --git a/src/syntax/tokens.rs b/src/syntax/tokens.rs
index ab6bc315..85e89be4 100644
--- a/src/syntax/tokens.rs
+++ b/src/syntax/tokens.rs
@@ -189,9 +189,11 @@ impl<'s> Iterator for Tokens<'s> {
// A string value.
'"' if self.state == TS::Function => {
let start = self.string_index();
+ let mut end = start;
let mut escaped = false;
- while let Some((_, c)) = self.chars.next() {
+ while let Some((index, c)) = self.chars.next() {
+ end = index;
if c == '"' && !escaped {
break;
}
@@ -199,7 +201,6 @@ impl<'s> Iterator for Tokens<'s> {
escaped = c == '\\';
}
- let end = self.string_index() - 1;
Token::Quoted(&self.src[start..end])
}
diff --git a/tests/layout.rs b/tests/layout.rs
index 096bc43f..007b3c3f 100644
--- a/tests/layout.rs
+++ b/tests/layout.rs
@@ -6,6 +6,8 @@ use std::io::{BufWriter, Write};
use std::panic;
use std::process::Command;
+use futures_executor::block_on;
+
use typstc::Typesetter;
use typstc::layout::{MultiLayout, Serialize};
use typstc::size::{Size, Size2D};
@@ -125,7 +127,7 @@ fn compile(typesetter: &Typesetter, src: &str) -> Option<MultiLayout> {
// Warmup.
let warmup_start = Instant::now();
- let is_ok = typesetter.typeset(&src).is_ok();
+ let is_ok = block_on(typesetter.typeset(&src)).is_ok();
let warmup_end = Instant::now();
// Only continue if the typesetting was successful.
@@ -133,7 +135,7 @@ fn compile(typesetter: &Typesetter, src: &str) -> Option<MultiLayout> {
let start = Instant::now();
let tree = typesetter.parse(&src).unwrap();
let mid = Instant::now();
- typesetter.layout(&tree).unwrap();
+ block_on(typesetter.layout(&tree)).unwrap();
let end = Instant::now();
println!(" - cold start: {:?}", warmup_end - warmup_start);
@@ -144,7 +146,7 @@ fn compile(typesetter: &Typesetter, src: &str) -> Option<MultiLayout> {
}
};
- match typesetter.typeset(&src) {
+ match block_on(typesetter.typeset(&src)) {
Ok(layouts) => Some(layouts),
Err(err) => {
println!(" - compilation failed: {}", err);