mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Split state and scopes, less ref-counting 🔀
This commit is contained in:
parent
0f0416054f
commit
d763f0f5a6
@ -1,14 +1,12 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
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;
|
||||||
|
|
||||||
use typst::env::{Env, ResourceLoader};
|
use typst::env::{Env, ResourceLoader};
|
||||||
use typst::eval::{eval, State};
|
use typst::eval::{eval, State};
|
||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::FontLoader;
|
use typst::font::FsIndexExt;
|
||||||
use typst::layout::layout;
|
use typst::layout::layout;
|
||||||
|
use typst::library;
|
||||||
use typst::parse::parse;
|
use typst::parse::parse;
|
||||||
use typst::typeset;
|
use typst::typeset;
|
||||||
|
|
||||||
@ -25,25 +23,24 @@ fn benchmarks(c: &mut Criterion) {
|
|||||||
let mut index = FsIndex::new();
|
let mut index = FsIndex::new();
|
||||||
index.search_dir(FONT_DIR);
|
index.search_dir(FONT_DIR);
|
||||||
|
|
||||||
let (files, descriptors) = index.into_vecs();
|
let mut env = Env {
|
||||||
let env = Rc::new(RefCell::new(Env {
|
fonts: index.into_dynamic_loader(),
|
||||||
fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors),
|
|
||||||
resources: ResourceLoader::new(),
|
resources: ResourceLoader::new(),
|
||||||
}));
|
};
|
||||||
|
|
||||||
|
let scope = library::new();
|
||||||
|
let state = State::default();
|
||||||
|
|
||||||
// Prepare intermediate results and run warm.
|
// Prepare intermediate results and run warm.
|
||||||
let state = State::default();
|
|
||||||
let syntax_tree = parse(COMA).output;
|
let syntax_tree = parse(COMA).output;
|
||||||
let layout_tree = eval(&syntax_tree, Rc::clone(&env), state.clone()).output;
|
let layout_tree = eval(&syntax_tree, &mut env, &scope, state.clone()).output;
|
||||||
let frames = layout(&layout_tree, Rc::clone(&env));
|
let frames = layout(&layout_tree, &mut env);
|
||||||
|
|
||||||
// Bench!
|
// Bench!
|
||||||
bench!("parse-coma": parse(COMA));
|
bench!("parse-coma": parse(COMA));
|
||||||
bench!("eval-coma": eval(&syntax_tree, Rc::clone(&env), state.clone()));
|
bench!("eval-coma": eval(&syntax_tree, &mut env, &scope, state.clone()));
|
||||||
bench!("layout-coma": layout(&layout_tree, Rc::clone(&env)));
|
bench!("layout-coma": layout(&layout_tree, &mut env));
|
||||||
bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone()));
|
bench!("typeset-coma": typeset(COMA, &mut env, &scope, state.clone()));
|
||||||
|
|
||||||
let env = env.borrow();
|
|
||||||
bench!("export-pdf-coma": pdf::export(&frames, &env));
|
bench!("export-pdf-coma": pdf::export(&frames, &env));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
//! Environment interactions.
|
//! Environment interactions.
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::{hash_map::Entry, HashMap};
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use image::io::Reader as ImageReader;
|
use image::io::Reader as ImageReader;
|
||||||
use image::{DynamicImage, GenericImageView, ImageFormat};
|
use image::{DynamicImage, GenericImageView, ImageFormat};
|
||||||
|
|
||||||
use crate::font::FontLoader;
|
use crate::font::FontLoader;
|
||||||
|
|
||||||
/// A reference-counted shared environment.
|
|
||||||
pub type SharedEnv = Rc<RefCell<Env>>;
|
|
||||||
|
|
||||||
/// Encapsulates all environment dependencies (fonts, resources).
|
/// Encapsulates all environment dependencies (fonts, resources).
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Env {
|
pub struct Env {
|
||||||
|
@ -8,7 +8,7 @@ impl Eval for Spanned<&ExprCall> {
|
|||||||
let name = &self.v.name.v;
|
let name = &self.v.name.v;
|
||||||
let span = self.v.name.span;
|
let span = self.v.name.span;
|
||||||
|
|
||||||
if let Some(value) = ctx.state.scope.get(name) {
|
if let Some(value) = ctx.scopes.get(name) {
|
||||||
if let Value::Func(func) = value {
|
if let Value::Func(func) = value {
|
||||||
let func = func.clone();
|
let func = func.clone();
|
||||||
ctx.deco(Deco::Resolved.with_span(span));
|
ctx.deco(Deco::Resolved.with_span(span));
|
||||||
@ -90,10 +90,10 @@ impl Args {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Filter out and remove all convertible positional arguments.
|
/// Filter out and remove all convertible positional arguments.
|
||||||
pub fn filter<'a, T>(
|
pub fn filter<'a, 'b: 'a, T>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
ctx: &'a mut EvalContext,
|
ctx: &'a mut EvalContext<'b>,
|
||||||
) -> impl Iterator<Item = T> + 'a
|
) -> impl Iterator<Item = T> + Captures<'a> + Captures<'b>
|
||||||
where
|
where
|
||||||
T: Cast<Spanned<Value>>,
|
T: Cast<Spanned<Value>>,
|
||||||
{
|
{
|
||||||
@ -130,6 +130,13 @@ impl Args {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is a workaround because `-> impl Trait + 'a + 'b` does not work.
|
||||||
|
//
|
||||||
|
// See also: https://github.com/rust-lang/rust/issues/49431
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub trait Captures<'a> {}
|
||||||
|
impl<'a, T: ?Sized> Captures<'a> for T {}
|
||||||
|
|
||||||
/// Cast the value into `T`, generating an error if the conversion fails.
|
/// Cast the value into `T`, generating an error if the conversion fails.
|
||||||
fn cast<T>(ctx: &mut EvalContext, value: Spanned<Value>) -> Option<T>
|
fn cast<T>(ctx: &mut EvalContext, value: Spanned<Value>) -> Option<T>
|
||||||
where
|
where
|
||||||
|
@ -6,7 +6,6 @@ use fontdock::FontStyle;
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::diag::Diag;
|
use crate::diag::Diag;
|
||||||
use crate::diag::{Deco, Feedback, Pass};
|
use crate::diag::{Deco, Feedback, Pass};
|
||||||
use crate::env::SharedEnv;
|
|
||||||
use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size};
|
use crate::geom::{ChildAlign, Dir, Gen, LayoutDirs, Length, Linear, Sides, Size};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
|
Expansion, Node, NodePad, NodePages, NodePar, NodeSpacing, NodeStack, NodeText, Tree,
|
||||||
@ -14,9 +13,11 @@ use crate::layout::{
|
|||||||
|
|
||||||
/// The context for evaluation.
|
/// The context for evaluation.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EvalContext {
|
pub struct EvalContext<'a> {
|
||||||
/// The environment from which resources are gathered.
|
/// The environment from which resources are gathered.
|
||||||
pub env: SharedEnv,
|
pub env: &'a mut Env,
|
||||||
|
/// The active scopes.
|
||||||
|
pub scopes: Scopes<'a>,
|
||||||
/// The active evaluation state.
|
/// The active evaluation state.
|
||||||
pub state: State,
|
pub state: State,
|
||||||
/// The accumulated feedback.
|
/// The accumulated feedback.
|
||||||
@ -34,11 +35,12 @@ pub struct EvalContext {
|
|||||||
inner: Vec<Node>,
|
inner: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EvalContext {
|
impl<'a> EvalContext<'a> {
|
||||||
/// Create a new evaluation context with a base state.
|
/// Create a new evaluation context with a base state and scope.
|
||||||
pub fn new(env: SharedEnv, state: State) -> Self {
|
pub fn new(env: &'a mut Env, scope: &'a Scope, state: State) -> Self {
|
||||||
Self {
|
Self {
|
||||||
env,
|
env,
|
||||||
|
scopes: Scopes::new(scope),
|
||||||
state,
|
state,
|
||||||
groups: vec![],
|
groups: vec![],
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
|
@ -18,17 +18,23 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use crate::color::Color;
|
use crate::color::Color;
|
||||||
use crate::diag::Pass;
|
use crate::diag::Pass;
|
||||||
use crate::env::SharedEnv;
|
use crate::env::Env;
|
||||||
use crate::geom::{Angle, Length, Relative, Spec};
|
use crate::geom::{Angle, Length, Relative, Spec};
|
||||||
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
|
use crate::layout::{self, Expansion, NodeSpacing, NodeStack};
|
||||||
use crate::syntax::*;
|
use crate::syntax::*;
|
||||||
|
|
||||||
/// Evaluate a syntax tree into a layout tree.
|
/// Evaluate a syntax tree into a layout tree.
|
||||||
///
|
///
|
||||||
/// The given `state` is the base state that may be updated over the course of
|
/// The `state` is the base state that may be updated over the course of
|
||||||
/// evaluation.
|
/// evaluation. The `scope` similarly consists of the base definitions that are
|
||||||
pub fn eval(tree: &Tree, env: SharedEnv, state: State) -> Pass<layout::Tree> {
|
/// present from the beginning (typically, the standard library).
|
||||||
let mut ctx = EvalContext::new(env, state);
|
pub fn eval(
|
||||||
|
tree: &Tree,
|
||||||
|
env: &mut Env,
|
||||||
|
scope: &Scope,
|
||||||
|
state: State,
|
||||||
|
) -> Pass<layout::Tree> {
|
||||||
|
let mut ctx = EvalContext::new(env, scope, state);
|
||||||
ctx.start_page_group(Softness::Hard);
|
ctx.start_page_group(Softness::Hard);
|
||||||
tree.eval(&mut ctx);
|
tree.eval(&mut ctx);
|
||||||
ctx.end_page_group(|s| s == Softness::Hard);
|
ctx.end_page_group(|s| s == Softness::Hard);
|
||||||
@ -118,7 +124,7 @@ impl Eval for Spanned<&NodeRaw> {
|
|||||||
|
|
||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
let prev = Rc::clone(&ctx.state.font.families);
|
let prev = Rc::clone(&ctx.state.font.families);
|
||||||
let families = Rc::make_mut(&mut ctx.state.font.families);
|
let families = ctx.state.font.families_mut();
|
||||||
families.list.insert(0, "monospace".to_string());
|
families.list.insert(0, "monospace".to_string());
|
||||||
families.flatten();
|
families.flatten();
|
||||||
|
|
||||||
@ -151,7 +157,7 @@ impl Eval for Spanned<&Expr> {
|
|||||||
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
fn eval(self, ctx: &mut EvalContext) -> Self::Output {
|
||||||
match self.v {
|
match self.v {
|
||||||
Expr::None => Value::None,
|
Expr::None => Value::None,
|
||||||
Expr::Ident(v) => match ctx.state.scope.get(v) {
|
Expr::Ident(v) => match ctx.scopes.get(v) {
|
||||||
Some(value) => value.clone(),
|
Some(value) => value.clone(),
|
||||||
None => {
|
None => {
|
||||||
ctx.diag(error!(self.span, "unknown variable"));
|
ctx.diag(error!(self.span, "unknown variable"));
|
||||||
@ -179,7 +185,7 @@ impl Eval for Spanned<&Expr> {
|
|||||||
Some(expr) => expr.as_ref().eval(ctx),
|
Some(expr) => expr.as_ref().eval(ctx),
|
||||||
None => Value::None,
|
None => Value::None,
|
||||||
};
|
};
|
||||||
Rc::make_mut(&mut ctx.state.scope).set(v.pat.v.as_str(), value);
|
ctx.scopes.define(v.pat.v.as_str(), value);
|
||||||
Value::None
|
Value::None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,58 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
use super::Value;
|
use super::Value;
|
||||||
|
|
||||||
/// A map from identifiers to values.
|
/// A hierarchy of scopes.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Scopes<'a> {
|
||||||
|
/// The active scope.
|
||||||
|
top: Scope,
|
||||||
|
/// The stack of lower scopes.
|
||||||
|
scopes: Vec<Scope>,
|
||||||
|
/// The base scope.
|
||||||
|
base: &'a Scope,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Scopes<'a> {
|
||||||
|
/// Create a new hierarchy of scopes.
|
||||||
|
pub fn new(base: &'a Scope) -> Self {
|
||||||
|
Self { top: Scope::new(), scopes: vec![], base }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up the value of a variable in the scopes.
|
||||||
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
|
iter::once(&self.top)
|
||||||
|
.chain(&self.scopes)
|
||||||
|
.chain(iter::once(self.base))
|
||||||
|
.find_map(|scope| scope.get(var))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define a variable in the active scope.
|
||||||
|
pub fn define(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
||||||
|
self.top.set(var, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A map from variable names to values.
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
values: HashMap<String, Value>,
|
values: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
// Create a new empty scope with a fallback function that is invoked when no
|
// Create a new empty scope.
|
||||||
// match is found.
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the value of the given variable.
|
/// Look up the value of a variable.
|
||||||
pub fn get(&self, var: &str) -> Option<&Value> {
|
pub fn get(&self, var: &str) -> Option<&Value> {
|
||||||
self.values.get(var)
|
self.values.get(var)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Store the value for the given variable.
|
/// Store the value for a variable.
|
||||||
pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
pub fn set(&mut self, var: impl Into<String>, value: impl Into<Value>) {
|
||||||
self.values.insert(var.into(), value.into());
|
self.values.insert(var.into(), value.into());
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
|
use fontdock::{fallback, FallbackTree, FontStretch, FontStyle, FontVariant, FontWeight};
|
||||||
|
|
||||||
use super::Scope;
|
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec,
|
Align, ChildAlign, Dir, LayoutDirs, Length, Linear, Relative, Sides, Size, Spec,
|
||||||
};
|
};
|
||||||
@ -12,14 +11,12 @@ use crate::paper::{Paper, PaperClass, PAPER_A4};
|
|||||||
/// The evaluation state.
|
/// The evaluation state.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
/// The scope that contains variable definitions.
|
|
||||||
pub scope: Rc<Scope>,
|
|
||||||
/// The current page state.
|
/// The current page state.
|
||||||
pub page: StatePage,
|
pub page: PageSettings,
|
||||||
/// The current paragraph state.
|
/// The current paragraph state.
|
||||||
pub par: StatePar,
|
pub par: ParSettings,
|
||||||
/// The current font state.
|
/// The current font state.
|
||||||
pub font: StateFont,
|
pub font: FontSettings,
|
||||||
/// The current directions.
|
/// The current directions.
|
||||||
pub dirs: LayoutDirs,
|
pub dirs: LayoutDirs,
|
||||||
/// The current alignments.
|
/// The current alignments.
|
||||||
@ -29,10 +26,9 @@ pub struct State {
|
|||||||
impl Default for State {
|
impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
scope: Rc::new(crate::library::_std()),
|
page: PageSettings::default(),
|
||||||
page: StatePage::default(),
|
par: ParSettings::default(),
|
||||||
par: StatePar::default(),
|
font: FontSettings::default(),
|
||||||
font: StateFont::default(),
|
|
||||||
dirs: LayoutDirs::new(Dir::TTB, Dir::LTR),
|
dirs: LayoutDirs::new(Dir::TTB, Dir::LTR),
|
||||||
align: ChildAlign::new(Align::Start, Align::Start),
|
align: ChildAlign::new(Align::Start, Align::Start),
|
||||||
}
|
}
|
||||||
@ -41,7 +37,7 @@ impl Default for State {
|
|||||||
|
|
||||||
/// Defines page properties.
|
/// Defines page properties.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct StatePage {
|
pub struct PageSettings {
|
||||||
/// The class of this page.
|
/// The class of this page.
|
||||||
pub class: PaperClass,
|
pub class: PaperClass,
|
||||||
/// The width and height of the page.
|
/// The width and height of the page.
|
||||||
@ -53,7 +49,7 @@ pub struct StatePage {
|
|||||||
pub margins: Sides<Option<Linear>>,
|
pub margins: Sides<Option<Linear>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatePage {
|
impl PageSettings {
|
||||||
/// The default page style for the given paper.
|
/// The default page style for the given paper.
|
||||||
pub fn new(paper: Paper) -> Self {
|
pub fn new(paper: Paper) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -76,7 +72,7 @@ impl StatePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StatePage {
|
impl Default for PageSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(PAPER_A4)
|
Self::new(PAPER_A4)
|
||||||
}
|
}
|
||||||
@ -84,7 +80,7 @@ impl Default for StatePage {
|
|||||||
|
|
||||||
/// Defines paragraph properties.
|
/// Defines paragraph properties.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct StatePar {
|
pub struct ParSettings {
|
||||||
/// The spacing between words (dependent on scaled font size).
|
/// The spacing between words (dependent on scaled font size).
|
||||||
pub word_spacing: Linear,
|
pub word_spacing: Linear,
|
||||||
/// The spacing between lines (dependent on scaled font size).
|
/// The spacing between lines (dependent on scaled font size).
|
||||||
@ -93,7 +89,7 @@ pub struct StatePar {
|
|||||||
pub par_spacing: Linear,
|
pub par_spacing: Linear,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StatePar {
|
impl Default for ParSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
word_spacing: Relative::new(0.25).into(),
|
word_spacing: Relative::new(0.25).into(),
|
||||||
@ -105,7 +101,7 @@ impl Default for StatePar {
|
|||||||
|
|
||||||
/// Defines font properties.
|
/// Defines font properties.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct StateFont {
|
pub struct FontSettings {
|
||||||
/// A tree of font family names and generic class names.
|
/// A tree of font family names and generic class names.
|
||||||
pub families: Rc<FallbackTree>,
|
pub families: Rc<FallbackTree>,
|
||||||
/// The selected font variant.
|
/// The selected font variant.
|
||||||
@ -122,14 +118,19 @@ pub struct StateFont {
|
|||||||
pub emph: bool,
|
pub emph: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateFont {
|
impl FontSettings {
|
||||||
|
/// Access the `families` mutably.
|
||||||
|
pub fn families_mut(&mut self) -> &mut FallbackTree {
|
||||||
|
Rc::make_mut(&mut self.families)
|
||||||
|
}
|
||||||
|
|
||||||
/// The absolute font size.
|
/// The absolute font size.
|
||||||
pub fn font_size(&self) -> Length {
|
pub fn font_size(&self) -> Length {
|
||||||
self.scale.resolve(self.size)
|
self.scale.resolve(self.size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StateFont {
|
impl Default for FontSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
/// The default tree of font fallbacks.
|
/// The default tree of font fallbacks.
|
||||||
|
18
src/font.rs
18
src/font.rs
@ -3,6 +3,9 @@
|
|||||||
use fontdock::{ContainsChar, FaceFromVec, FontSource};
|
use fontdock::{ContainsChar, FaceFromVec, FontSource};
|
||||||
use ttf_parser::Face;
|
use ttf_parser::Face;
|
||||||
|
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
use fontdock::fs::{FsIndex, FsSource};
|
||||||
|
|
||||||
/// A font loader that is backed by a dynamic source.
|
/// A font loader that is backed by a dynamic source.
|
||||||
pub type FontLoader = fontdock::FontLoader<Box<dyn FontSource<Face = FaceBuf>>>;
|
pub type FontLoader = fontdock::FontLoader<Box<dyn FontSource<Face = FaceBuf>>>;
|
||||||
|
|
||||||
@ -47,3 +50,18 @@ impl ContainsChar for FaceBuf {
|
|||||||
self.get().glyph_index(c).is_some()
|
self.get().glyph_index(c).is_some()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
pub trait FsIndexExt {
|
||||||
|
/// Create a font loader backed by a boxed [`FsSource`] which serves all
|
||||||
|
/// indexed font faces.
|
||||||
|
fn into_dynamic_loader(self) -> FontLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fs")]
|
||||||
|
impl FsIndexExt for FsIndex {
|
||||||
|
fn into_dynamic_loader(self) -> FontLoader {
|
||||||
|
let (files, descriptors) = self.into_vecs();
|
||||||
|
FontLoader::new(Box::new(FsSource::new(files)), descriptors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -8,7 +8,7 @@ mod spacing;
|
|||||||
mod stack;
|
mod stack;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
use crate::env::{ResourceId, SharedEnv};
|
use crate::env::{Env, ResourceId};
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::shaping::Shaped;
|
use crate::shaping::Shaped;
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ pub use stack::*;
|
|||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
|
||||||
/// Layout a tree into a collection of frames.
|
/// Layout a tree into a collection of frames.
|
||||||
pub fn layout(tree: &Tree, env: SharedEnv) -> Vec<Frame> {
|
pub fn layout(tree: &Tree, env: &mut Env) -> Vec<Frame> {
|
||||||
tree.layout(&mut LayoutContext { env })
|
tree.layout(&mut LayoutContext { env })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,10 +65,10 @@ pub trait Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct LayoutContext {
|
pub struct LayoutContext<'a> {
|
||||||
/// The environment from which fonts are gathered.
|
/// The environment from which fonts are gathered.
|
||||||
pub env: SharedEnv,
|
pub env: &'a mut Env,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection of areas to layout into.
|
/// A collection of areas to layout into.
|
||||||
|
@ -25,13 +25,12 @@ pub struct NodeText {
|
|||||||
|
|
||||||
impl Layout for NodeText {
|
impl Layout for NodeText {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
|
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
|
||||||
let mut env = ctx.env.borrow_mut();
|
|
||||||
Layouted::Frame(
|
Layouted::Frame(
|
||||||
shaping::shape(
|
shaping::shape(
|
||||||
&self.text,
|
&self.text,
|
||||||
self.dir,
|
self.dir,
|
||||||
self.font_size,
|
self.font_size,
|
||||||
&mut env.fonts,
|
&mut ctx.env.fonts,
|
||||||
&self.families,
|
&self.families,
|
||||||
self.variant,
|
self.variant,
|
||||||
),
|
),
|
||||||
|
15
src/lib.rs
15
src/lib.rs
@ -42,18 +42,21 @@ pub mod pretty;
|
|||||||
pub mod shaping;
|
pub mod shaping;
|
||||||
pub mod syntax;
|
pub mod syntax;
|
||||||
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use crate::diag::{Feedback, Pass};
|
use crate::diag::{Feedback, Pass};
|
||||||
use crate::env::SharedEnv;
|
use crate::env::Env;
|
||||||
use crate::eval::State;
|
use crate::eval::{Scope, State};
|
||||||
use crate::layout::Frame;
|
use crate::layout::Frame;
|
||||||
|
|
||||||
/// Process _Typst_ source code directly into a collection of frames.
|
/// Process _Typst_ source code directly into a collection of frames.
|
||||||
pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass<Vec<Frame>> {
|
pub fn typeset(
|
||||||
|
src: &str,
|
||||||
|
env: &mut Env,
|
||||||
|
scope: &Scope,
|
||||||
|
state: State,
|
||||||
|
) -> Pass<Vec<Frame>> {
|
||||||
let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src);
|
let Pass { output: syntax_tree, feedback: f1 } = parse::parse(src);
|
||||||
let Pass { output: layout_tree, feedback: f2 } =
|
let Pass { output: layout_tree, feedback: f2 } =
|
||||||
eval::eval(&syntax_tree, Rc::clone(&env), state);
|
eval::eval(&syntax_tree, env, scope, state);
|
||||||
let frames = layout::layout(&layout_tree, env);
|
let frames = layout::layout(&layout_tree, env);
|
||||||
Pass::new(frames, Feedback::join(f1, f2))
|
Pass::new(frames, Feedback::join(f1, f2))
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,9 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
let height = args.get(ctx, "height");
|
let height = args.get(ctx, "height");
|
||||||
|
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
let mut env = ctx.env.borrow_mut();
|
let loaded = ctx.env.resources.load(path.v, ImageResource::parse);
|
||||||
let loaded = env.resources.load(path.v, ImageResource::parse);
|
|
||||||
|
|
||||||
if let Some((res, img)) = loaded {
|
if let Some((res, img)) = loaded {
|
||||||
let dimensions = img.buf.dimensions();
|
let dimensions = img.buf.dimensions();
|
||||||
drop(env);
|
|
||||||
ctx.push(NodeImage {
|
ctx.push(NodeImage {
|
||||||
res,
|
res,
|
||||||
dimensions,
|
dimensions,
|
||||||
@ -30,7 +27,6 @@ pub fn image(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
align: ctx.state.align,
|
align: ctx.state.align,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
drop(env);
|
|
||||||
ctx.diag(error!(path.span, "failed to load image"));
|
ctx.diag(error!(path.span, "failed to load image"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
//! The standard library.
|
//! The standard library.
|
||||||
|
//!
|
||||||
|
//! Call [`new`] to obtain a [`Scope`] containing all standard library
|
||||||
|
//! definitions.
|
||||||
|
|
||||||
mod extend;
|
mod extend;
|
||||||
mod insert;
|
mod insert;
|
||||||
@ -15,8 +18,8 @@ use fontdock::{FontStretch, FontStyle, FontWeight};
|
|||||||
use crate::eval::{Scope, ValueAny, ValueFunc};
|
use crate::eval::{Scope, ValueAny, ValueFunc};
|
||||||
use crate::geom::Dir;
|
use crate::geom::Dir;
|
||||||
|
|
||||||
/// The scope containing the standard library.
|
/// Construct a scope containing all standard library definitions.
|
||||||
pub fn _std() -> Scope {
|
pub fn new() -> Scope {
|
||||||
let mut std = Scope::new();
|
let mut std = Scope::new();
|
||||||
macro_rules! set {
|
macro_rules! set {
|
||||||
(func: $name:expr, $func:expr) => {
|
(func: $name:expr, $func:expr) => {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use fontdock::{FontStretch, FontStyle, FontWeight};
|
use fontdock::{FontStretch, FontStyle, FontWeight};
|
||||||
|
|
||||||
@ -69,7 +68,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
|
|
||||||
let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect();
|
let list: Vec<_> = args.filter::<FontFamily>(ctx).map(|f| f.to_string()).collect();
|
||||||
if !list.is_empty() {
|
if !list.is_empty() {
|
||||||
let families = Rc::make_mut(&mut ctx.state.font.families);
|
let families = ctx.state.font.families_mut();
|
||||||
families.list = list;
|
families.list = list;
|
||||||
families.flatten();
|
families.flatten();
|
||||||
}
|
}
|
||||||
@ -89,7 +88,7 @@ pub fn font(ctx: &mut EvalContext, args: &mut Args) -> Value {
|
|||||||
for variant in FontFamily::VARIANTS {
|
for variant in FontFamily::VARIANTS {
|
||||||
if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) {
|
if let Some(FontFamilies(list)) = args.get(ctx, variant.as_str()) {
|
||||||
let strings = list.into_iter().map(|f| f.to_string()).collect();
|
let strings = list.into_iter().map(|f| f.to_string()).collect();
|
||||||
let families = Rc::make_mut(&mut ctx.state.font.families);
|
let families = ctx.state.font.families_mut();
|
||||||
families.update_class_list(variant.to_string(), strings);
|
families.update_class_list(variant.to_string(), strings);
|
||||||
families.flatten();
|
families.flatten();
|
||||||
}
|
}
|
||||||
|
20
src/main.rs
20
src/main.rs
@ -1,16 +1,15 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context};
|
use anyhow::{anyhow, bail, Context};
|
||||||
use fontdock::fs::{FsIndex, FsSource};
|
use fontdock::fs::FsIndex;
|
||||||
|
|
||||||
use typst::diag::{Feedback, Pass};
|
use typst::diag::{Feedback, Pass};
|
||||||
use typst::env::{Env, ResourceLoader};
|
use typst::env::{Env, ResourceLoader};
|
||||||
use typst::eval::State;
|
use typst::eval::State;
|
||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::FontLoader;
|
use typst::font::FsIndexExt;
|
||||||
|
use typst::library;
|
||||||
use typst::parse::LineMap;
|
use typst::parse::LineMap;
|
||||||
use typst::typeset;
|
use typst::typeset;
|
||||||
|
|
||||||
@ -41,17 +40,18 @@ fn main() -> anyhow::Result<()> {
|
|||||||
index.search_dir("fonts");
|
index.search_dir("fonts");
|
||||||
index.search_system();
|
index.search_system();
|
||||||
|
|
||||||
let (files, descriptors) = index.into_vecs();
|
let mut env = Env {
|
||||||
let env = Rc::new(RefCell::new(Env {
|
fonts: index.into_dynamic_loader(),
|
||||||
fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors),
|
|
||||||
resources: ResourceLoader::new(),
|
resources: ResourceLoader::new(),
|
||||||
}));
|
};
|
||||||
|
|
||||||
|
let scope = library::new();
|
||||||
let state = State::default();
|
let state = State::default();
|
||||||
|
|
||||||
let Pass {
|
let Pass {
|
||||||
output: frames,
|
output: frames,
|
||||||
feedback: Feedback { mut diags, .. },
|
feedback: Feedback { mut diags, .. },
|
||||||
} = typeset(&src, Rc::clone(&env), state);
|
} = typeset(&src, &mut env, &scope, state);
|
||||||
|
|
||||||
if !diags.is_empty() {
|
if !diags.is_empty() {
|
||||||
diags.sort();
|
diags.sort();
|
||||||
@ -72,7 +72,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pdf_data = pdf::export(&frames, &env.borrow());
|
let pdf_data = pdf::export(&frames, &env);
|
||||||
fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?;
|
fs::write(&dest_path, pdf_data).context("Failed to write PDF file.")?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use fontdock::fs::{FsIndex, FsSource};
|
use fontdock::fs::FsIndex;
|
||||||
use image::{GenericImageView, Rgba};
|
use image::{GenericImageView, Rgba};
|
||||||
use tiny_skia::{
|
use tiny_skia::{
|
||||||
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
||||||
@ -15,12 +13,13 @@ use ttf_parser::OutlineBuilder;
|
|||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use typst::diag::{Diag, Feedback, Level, Pass};
|
use typst::diag::{Diag, Feedback, Level, Pass};
|
||||||
use typst::env::{Env, ImageResource, ResourceLoader, SharedEnv};
|
use typst::env::{Env, ImageResource, ResourceLoader};
|
||||||
use typst::eval::{Args, EvalContext, Scope, State, Value, ValueFunc};
|
use typst::eval::{Args, EvalContext, Scope, State, Value, ValueFunc};
|
||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::FontLoader;
|
use typst::font::FsIndexExt;
|
||||||
use typst::geom::{Length, Point, Sides, Size, Spec};
|
use typst::geom::{Length, Point, Sides, Size, Spec};
|
||||||
use typst::layout::{Element, Expansion, Frame, Image};
|
use typst::layout::{Element, Expansion, Frame, Image};
|
||||||
|
use typst::library;
|
||||||
use typst::parse::{LineMap, Scanner};
|
use typst::parse::{LineMap, Scanner};
|
||||||
use typst::pretty::{Pretty, Printer};
|
use typst::pretty::{Pretty, Printer};
|
||||||
use typst::shaping::Shaped;
|
use typst::shaping::Shaped;
|
||||||
@ -61,11 +60,10 @@ fn main() {
|
|||||||
let mut index = FsIndex::new();
|
let mut index = FsIndex::new();
|
||||||
index.search_dir(FONT_DIR);
|
index.search_dir(FONT_DIR);
|
||||||
|
|
||||||
let (files, descriptors) = index.into_vecs();
|
let mut env = Env {
|
||||||
let env = Rc::new(RefCell::new(Env {
|
fonts: index.into_dynamic_loader(),
|
||||||
fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors),
|
|
||||||
resources: ResourceLoader::new(),
|
resources: ResourceLoader::new(),
|
||||||
}));
|
};
|
||||||
|
|
||||||
let playground = Path::new("playground.typ");
|
let playground = Path::new("playground.typ");
|
||||||
if playground.exists() && filtered.is_empty() {
|
if playground.exists() && filtered.is_empty() {
|
||||||
@ -74,7 +72,7 @@ fn main() {
|
|||||||
Path::new("playground.png"),
|
Path::new("playground.png"),
|
||||||
Path::new("playground.pdf"),
|
Path::new("playground.pdf"),
|
||||||
None,
|
None,
|
||||||
&env,
|
&mut env,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +82,7 @@ fn main() {
|
|||||||
let png_path = Path::new(PNG_DIR).join(&relative).with_extension("png");
|
let png_path = Path::new(PNG_DIR).join(&relative).with_extension("png");
|
||||||
let pdf_path = Path::new(PDF_DIR).join(&relative).with_extension("pdf");
|
let pdf_path = Path::new(PDF_DIR).join(&relative).with_extension("pdf");
|
||||||
let ref_path = Path::new(REF_DIR).join(&relative).with_extension("png");
|
let ref_path = Path::new(REF_DIR).join(&relative).with_extension("png");
|
||||||
ok &= test(&src_path, &png_path, &pdf_path, Some(&ref_path), &env);
|
ok &= test(&src_path, &png_path, &pdf_path, Some(&ref_path), &mut env);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -127,7 +125,7 @@ fn test(
|
|||||||
png_path: &Path,
|
png_path: &Path,
|
||||||
pdf_path: &Path,
|
pdf_path: &Path,
|
||||||
ref_path: Option<&Path>,
|
ref_path: Option<&Path>,
|
||||||
env: &SharedEnv,
|
env: &mut Env,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
let name = src_path.strip_prefix(TYP_DIR).unwrap_or(src_path);
|
||||||
println!("Testing {}", name.display());
|
println!("Testing {}", name.display());
|
||||||
@ -143,7 +141,6 @@ fn test(
|
|||||||
frames.extend(part_frames);
|
frames.extend(part_frames);
|
||||||
}
|
}
|
||||||
|
|
||||||
let env = env.borrow();
|
|
||||||
if !frames.is_empty() {
|
if !frames.is_empty() {
|
||||||
let pdf_data = pdf::export(&frames, &env);
|
let pdf_data = pdf::export(&frames, &env);
|
||||||
fs::create_dir_all(&pdf_path.parent().unwrap()).unwrap();
|
fs::create_dir_all(&pdf_path.parent().unwrap()).unwrap();
|
||||||
@ -173,23 +170,24 @@ fn test(
|
|||||||
ok
|
ok
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_part(i: usize, src: &str, env: &SharedEnv) -> (bool, Vec<Frame>) {
|
fn test_part(i: usize, src: &str, env: &mut Env) -> (bool, Vec<Frame>) {
|
||||||
let map = LineMap::new(src);
|
let map = LineMap::new(src);
|
||||||
let (compare_ref, ref_diags) = parse_metadata(src, &map);
|
let (compare_ref, ref_diags) = parse_metadata(src, &map);
|
||||||
|
|
||||||
let mut state = State::default();
|
let mut scope = library::new();
|
||||||
|
register_helpers(&mut scope);
|
||||||
|
|
||||||
// We want to have "unbounded" pages, so we allow them to be infinitely
|
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||||
// large and fit them to match their content.
|
// large and fit them to match their content.
|
||||||
|
let mut state = State::default();
|
||||||
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
|
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
|
||||||
state.page.expand = Spec::new(Expansion::Fill, Expansion::Fit);
|
state.page.expand = Spec::new(Expansion::Fill, Expansion::Fit);
|
||||||
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
|
state.page.margins = Sides::uniform(Some(Length::pt(10.0).into()));
|
||||||
register_helpers(Rc::make_mut(&mut state.scope));
|
|
||||||
|
|
||||||
let Pass {
|
let Pass {
|
||||||
output: mut frames,
|
output: mut frames,
|
||||||
feedback: Feedback { mut diags, .. },
|
feedback: Feedback { mut diags, .. },
|
||||||
} = typeset(&src, Rc::clone(env), state);
|
} = typeset(&src, env, &scope, state);
|
||||||
|
|
||||||
if !compare_ref {
|
if !compare_ref {
|
||||||
frames.clear();
|
frames.clear();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user