Synchronous layout 🪀

This commit is contained in:
Laurenz 2020-10-12 17:10:01 +02:00
parent e94627721d
commit 38157b0e0c
16 changed files with 43 additions and 68 deletions

View File

@ -7,11 +7,6 @@ edition = "2018"
[workspace] [workspace]
members = ["main"] members = ["main"]
[features]
default = ["serialize", "fs"]
serialize = ["serde"]
fs = ["fontdock/fs"]
[lib] [lib]
bench = false bench = false
@ -21,19 +16,22 @@ opt-level = 2
[profile.release] [profile.release]
lto = true lto = true
[features]
default = ["fs"]
fs = ["fontdock/fs"]
[dependencies] [dependencies]
async-trait = "0.1"
fontdock = { path = "../fontdock", default-features = false } fontdock = { path = "../fontdock", default-features = false }
tide = { path = "../tide" } tide = { path = "../tide" }
ttf-parser = "0.8.2" ttf-parser = "0.8.2"
unicode-xid = "0.2" unicode-xid = "0.2"
# feature = "serde"
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = "0.3"
futures-executor = "0.3"
kurbo = "0.6.3" kurbo = "0.6.3"
serde_json = "1"
raqote = { version = "0.8", default-features = false } raqote = { version = "0.8", default-features = false }
[[test]] [[test]]

View File

@ -3,7 +3,6 @@ use std::rc::Rc;
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use fontdock::fs::{FsIndex, FsSource}; use fontdock::fs::{FsIndex, FsSource};
use futures_executor::block_on;
use typstc::eval::{eval, State}; use typstc::eval::{eval, State};
use typstc::font::FontLoader; use typstc::font::FontLoader;
@ -28,15 +27,15 @@ fn benchmarks(c: &mut Criterion) {
let tree = parse(COMA).output; let tree = parse(COMA).output;
let document = eval(&tree, state.clone()).output; let document = eval(&tree, state.clone()).output;
let _ = block_on(layout(&document, Rc::clone(&loader))); let _ = layout(&document, Rc::clone(&loader));
c.bench_function("parse-coma", |b| b.iter(|| parse(COMA))); c.bench_function("parse-coma", |b| b.iter(|| parse(COMA)));
c.bench_function("eval-coma", |b| b.iter(|| eval(&tree, state.clone()))); c.bench_function("eval-coma", |b| b.iter(|| eval(&tree, state.clone())));
c.bench_function("layout-coma", |b| { c.bench_function("layout-coma", |b| {
b.iter(|| block_on(layout(&document, Rc::clone(&loader)))) b.iter(|| layout(&document, Rc::clone(&loader)))
}); });
c.bench_function("typeset-coma", |b| { c.bench_function("typeset-coma", |b| {
b.iter(|| block_on(typeset(COMA, state.clone(), Rc::clone(&loader)))) b.iter(|| typeset(COMA, state.clone(), Rc::clone(&loader)))
}); });
} }

View File

@ -5,7 +5,6 @@ use std::path::{Path, PathBuf};
use std::rc::Rc; use std::rc::Rc;
use fontdock::fs::{FsIndex, FsSource}; use fontdock::fs::{FsIndex, FsSource};
use futures_executor::block_on;
use typstc::diag::{Feedback, Pass}; use typstc::diag::{Feedback, Pass};
use typstc::eval::State; use typstc::eval::State;
@ -48,7 +47,7 @@ fn main() {
let Pass { let Pass {
output: layouts, output: layouts,
feedback: Feedback { mut diags, .. }, feedback: Feedback { mut diags, .. },
} = block_on(typeset(&src, state, Rc::clone(&loader))); } = typeset(&src, state, Rc::clone(&loader));
if !diags.is_empty() { if !diags.is_empty() {
diags.sort(); diags.sort();

View File

@ -68,7 +68,7 @@ impl Feedback {
/// A diagnostic that arose in parsing or layouting. /// A diagnostic that arose in parsing or layouting.
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Diag { pub struct Diag {
/// How severe / important the diagnostic is. /// How severe / important the diagnostic is.
pub level: Level, pub level: Level,
@ -78,8 +78,8 @@ pub struct Diag {
/// How severe / important a diagnostic is. /// How severe / important a diagnostic is.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Level { pub enum Level {
Warning, Warning,
Error, Error,
@ -103,8 +103,8 @@ impl Display for Level {
/// Decorations for semantic syntax highlighting. /// Decorations for semantic syntax highlighting.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
pub enum Deco { pub enum Deco {
/// Emphasized text. /// Emphasized text.
Emph, Emph,

View File

@ -9,10 +9,10 @@ pub struct Document {
impl Document { impl Document {
/// Layout the document. /// Layout the document.
pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> { pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
let mut layouts = vec![]; let mut layouts = vec![];
for run in &self.runs { for run in &self.runs {
layouts.extend(run.layout(ctx).await); layouts.extend(run.layout(ctx));
} }
layouts layouts
} }
@ -31,9 +31,9 @@ pub struct Pages {
impl Pages { impl Pages {
/// Layout the page run. /// Layout the page run.
pub async fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> { pub fn layout(&self, ctx: &mut LayoutContext) -> Vec<BoxLayout> {
let areas = Areas::repeat(self.size); let areas = Areas::repeat(self.size);
let layouted = self.child.layout(ctx, &areas).await; let layouted = self.child.layout(ctx, &areas);
layouted.into_iter().filter_map(Layouted::into_boxed).collect() layouted.into_iter().filter_map(Layouted::into_boxed).collect()
} }
} }

View File

@ -12,9 +12,8 @@ pub struct Fixed {
pub child: LayoutNode, pub child: LayoutNode,
} }
#[async_trait(?Send)]
impl Layout for Fixed { impl Layout for Fixed {
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
let Area { rem, full } = areas.current; let Area { rem, full } = areas.current;
let size = Size::new( let size = Size::new(
self.width.map(|w| w.eval(full.width)).unwrap_or(rem.width), self.width.map(|w| w.eval(full.width)).unwrap_or(rem.width),
@ -22,7 +21,7 @@ impl Layout for Fixed {
); );
let areas = Areas::once(size); let areas = Areas::once(size);
self.child.layout(ctx, &areas).await self.child.layout(ctx, &areas)
} }
} }

View File

@ -9,8 +9,6 @@ mod spacing;
mod stack; mod stack;
mod text; mod text;
use async_trait::async_trait;
use crate::font::SharedFontLoader; use crate::font::SharedFontLoader;
use crate::geom::*; use crate::geom::*;
use crate::shaping::Shaped; use crate::shaping::Shaped;
@ -25,9 +23,9 @@ pub use stack::*;
pub use text::*; pub use text::*;
/// Layout a document and return the produced layouts. /// Layout a document and return the produced layouts.
pub async fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> { pub fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> {
let mut ctx = LayoutContext { loader }; let mut ctx = LayoutContext { loader };
document.layout(&mut ctx).await document.layout(&mut ctx)
} }
/// The context for layouting. /// The context for layouting.
@ -38,20 +36,9 @@ pub struct LayoutContext {
} }
/// Layout a node. /// Layout a node.
#[async_trait(?Send)]
pub trait Layout { pub trait Layout {
/// Layout the node in the given layout context. /// Layout the node in the given layout context.
/// fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted>;
/// This signature looks pretty horrible due to async in trait methods, but
/// it's actually just the following:
/// ```rust,ignore
/// async fn layout(
/// &self,
/// ctx: &mut LayoutContext,
/// constraints: LayoutConstraints,
/// ) -> Vec<LayoutItem>;
/// ```
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted>;
} }
/// A sequence of areas to layout into. /// A sequence of areas to layout into.

View File

@ -24,13 +24,12 @@ impl LayoutNode {
} }
} }
#[async_trait(?Send)]
impl Layout for LayoutNode { impl Layout for LayoutNode {
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
match self { match self {
Self::Spacing(spacing) => spacing.layout(ctx, areas).await, Self::Spacing(spacing) => spacing.layout(ctx, areas),
Self::Text(text) => text.layout(ctx, areas).await, Self::Text(text) => text.layout(ctx, areas),
Self::Dyn(boxed) => boxed.layout(ctx, areas).await, Self::Dyn(boxed) => boxed.layout(ctx, areas),
} }
} }
} }

View File

@ -10,9 +10,8 @@ pub struct Pad {
pub child: LayoutNode, pub child: LayoutNode,
} }
#[async_trait(?Send)]
impl Layout for Pad { impl Layout for Pad {
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
let shrink = |size| size - self.padding.eval(size).size(); let shrink = |size| size - self.padding.eval(size).size();
let areas = Areas { let areas = Areas {
current: Area { current: Area {
@ -23,7 +22,7 @@ impl Layout for Pad {
last: areas.last.map(shrink), last: areas.last.map(shrink),
}; };
let mut layouted = self.child.layout(ctx, &areas).await; let mut layouted = self.child.layout(ctx, &areas);
for item in &mut layouted { for item in &mut layouted {
if let Layouted::Boxed(boxed, _) = item { if let Layouted::Boxed(boxed, _) = item {

View File

@ -18,12 +18,11 @@ pub struct Par {
pub children: Vec<LayoutNode>, pub children: Vec<LayoutNode>,
} }
#[async_trait(?Send)]
impl Layout for Par { impl Layout for Par {
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
let mut layouter = ParLayouter::new(self, areas.clone()); let mut layouter = ParLayouter::new(self, areas.clone());
for child in &self.children { for child in &self.children {
for layouted in child.layout(ctx, &layouter.areas).await { for layouted in child.layout(ctx, &layouter.areas) {
match layouted { match layouted {
Layouted::Spacing(spacing) => layouter.spacing(spacing), Layouted::Spacing(spacing) => layouter.spacing(spacing),
Layouted::Boxed(boxed, aligns) => layouter.boxed(boxed, aligns.cross), Layouted::Boxed(boxed, aligns) => layouter.boxed(boxed, aligns.cross),

View File

@ -14,9 +14,8 @@ pub struct Spacing {
pub softness: Softness, pub softness: Softness,
} }
#[async_trait(?Send)]
impl Layout for Spacing { impl Layout for Spacing {
async fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Vec<Layouted> { fn layout(&self, _: &mut LayoutContext, _: &Areas) -> Vec<Layouted> {
vec![Layouted::Spacing(self.amount)] vec![Layouted::Spacing(self.amount)]
} }
} }

View File

@ -16,12 +16,11 @@ pub struct Stack {
pub children: Vec<LayoutNode>, pub children: Vec<LayoutNode>,
} }
#[async_trait(?Send)]
impl Layout for Stack { impl Layout for Stack {
async fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> { fn layout(&self, ctx: &mut LayoutContext, areas: &Areas) -> Vec<Layouted> {
let mut layouter = StackLayouter::new(self, areas.clone()); let mut layouter = StackLayouter::new(self, areas.clone());
for child in &self.children { for child in &self.children {
for layouted in child.layout(ctx, &layouter.areas).await { for layouted in child.layout(ctx, &layouter.areas) {
match layouted { match layouted {
Layouted::Spacing(spacing) => layouter.spacing(spacing), Layouted::Spacing(spacing) => layouter.spacing(spacing),
Layouted::Boxed(boxed, aligns) => layouter.boxed(boxed, aligns), Layouted::Boxed(boxed, aligns) => layouter.boxed(boxed, aligns),

View File

@ -23,9 +23,8 @@ pub struct Text {
pub aligns: Gen<Align>, pub aligns: Gen<Align>,
} }
#[async_trait(?Send)]
impl Layout for Text { impl Layout for Text {
async fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Vec<Layouted> { fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Vec<Layouted> {
let mut loader = ctx.loader.borrow_mut(); let mut loader = ctx.loader.borrow_mut();
vec![Layouted::Boxed( vec![Layouted::Boxed(
shaping::shape( shaping::shape(

View File

@ -50,13 +50,13 @@ use crate::font::SharedFontLoader;
use crate::layout::BoxLayout; use crate::layout::BoxLayout;
/// Process _Typst_ source code directly into a collection of layouts. /// Process _Typst_ source code directly into a collection of layouts.
pub async fn typeset( pub fn typeset(
src: &str, src: &str,
state: State, state: State,
loader: SharedFontLoader, loader: SharedFontLoader,
) -> Pass<Vec<BoxLayout>> { ) -> Pass<Vec<BoxLayout>> {
let Pass { output: tree, feedback: f1 } = parse::parse(src); let Pass { output: tree, feedback: f1 } = parse::parse(src);
let Pass { output: document, feedback: f2 } = eval::eval(&tree, state); let Pass { output: document, feedback: f2 } = eval::eval(&tree, state);
let layouts = layout::layout(&document, loader).await; let layouts = layout::layout(&document, loader);
Pass::new(layouts, Feedback::join(f1, f2)) Pass::new(layouts, Feedback::join(f1, f2))
} }

View File

@ -41,7 +41,7 @@ impl<T> Offset for SpanVec<T> {
/// A value with the span it corresponds to in the source code. /// A value with the span it corresponds to in the source code.
#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Spanned<T> { pub struct Spanned<T> {
/// The spanned value. /// The spanned value.
pub v: T, pub v: T,
@ -109,7 +109,7 @@ impl<T: Debug> Debug for Spanned<T> {
/// Locates a slice of source code. /// Locates a slice of source code.
#[derive(Copy, Clone, Ord, PartialOrd)] #[derive(Copy, Clone, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Span { pub struct Span {
/// The inclusive start position. /// The inclusive start position.
pub start: Pos, pub start: Pos,
@ -210,7 +210,7 @@ impl Debug for Span {
/// A byte position in source code. /// A byte position in source code.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Pos(pub u32); pub struct Pos(pub u32);
impl Pos { impl Pos {
@ -255,7 +255,7 @@ impl Debug for Pos {
/// A one-indexed line-column position in source code. /// A one-indexed line-column position in source code.
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Location { pub struct Location {
/// The one-indexed line. /// The one-indexed line.
pub line: u32, pub line: u32,

View File

@ -7,7 +7,6 @@ use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use fontdock::fs::{FsIndex, FsSource}; use fontdock::fs::{FsIndex, FsSource};
use futures_executor::block_on;
use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector}; use raqote::{DrawTarget, PathBuilder, SolidSource, Source, Transform, Vector};
use ttf_parser::OutlineBuilder; use ttf_parser::OutlineBuilder;
@ -77,7 +76,7 @@ fn test(name: &str, src: &str, src_path: &Path, loader: &SharedFontLoader) {
let Pass { let Pass {
output: layouts, output: layouts,
feedback: Feedback { mut diags, .. }, feedback: Feedback { mut diags, .. },
} = block_on(typeset(&src, state, Rc::clone(loader))); } = typeset(&src, state, Rc::clone(loader));
if !diags.is_empty() { if !diags.is_empty() {
diags.sort(); diags.sort();