mirror of
https://github.com/typst/typst
synced 2025-05-13 20:46:23 +08:00
Main context struct
This commit is contained in:
parent
8000783f95
commit
9488b1b850
@ -4,37 +4,41 @@ use std::rc::Rc;
|
||||
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use typst::cache::Cache;
|
||||
use typst::eval::{eval, Module, Scope};
|
||||
use typst::exec::{exec, State};
|
||||
use typst::eval::{eval, Module};
|
||||
use typst::exec::exec;
|
||||
use typst::export::pdf;
|
||||
use typst::layout::{layout, Frame, LayoutTree};
|
||||
use typst::loading::{FileId, FsLoader};
|
||||
use typst::parse::parse;
|
||||
use typst::syntax::SyntaxTree;
|
||||
use typst::typeset;
|
||||
use typst::Context;
|
||||
|
||||
const FONT_DIR: &str = "../fonts";
|
||||
const TYP_DIR: &str = "../tests/typ";
|
||||
const CASES: &[&str] = &["coma.typ", "text/basic.typ"];
|
||||
|
||||
fn benchmarks(c: &mut Criterion) {
|
||||
let ctx = Context::new();
|
||||
let loader = {
|
||||
let mut loader = FsLoader::new();
|
||||
loader.search_path(FONT_DIR);
|
||||
Rc::new(loader)
|
||||
};
|
||||
|
||||
let ctx = Rc::new(RefCell::new(Context::new(loader.clone())));
|
||||
|
||||
for case in CASES {
|
||||
let path = Path::new(TYP_DIR).join(case);
|
||||
let name = path.file_stem().unwrap().to_string_lossy();
|
||||
let src_id = ctx.borrow_mut().loader.resolve_path(&path).unwrap();
|
||||
let src_id = loader.resolve_path(&path).unwrap();
|
||||
let src = std::fs::read_to_string(&path).unwrap();
|
||||
let case = Case::new(src_id, src, ctx.clone());
|
||||
|
||||
macro_rules! bench {
|
||||
($step:literal, setup = |$cache:ident| $setup:expr, code = $code:expr $(,)?) => {
|
||||
($step:literal, setup = |$ctx:ident| $setup:expr, code = $code:expr $(,)?) => {
|
||||
c.bench_function(&format!("{}-{}", $step, name), |b| {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
let mut borrowed = ctx.borrow_mut();
|
||||
let $cache = &mut borrowed.cache;
|
||||
let mut $ctx = ctx.borrow_mut();
|
||||
$setup
|
||||
},
|
||||
|_| $code,
|
||||
@ -61,12 +65,12 @@ fn benchmarks(c: &mut Criterion) {
|
||||
{
|
||||
bench!(
|
||||
"layout",
|
||||
setup = |cache| cache.layout.clear(),
|
||||
setup = |ctx| ctx.layouts.clear(),
|
||||
code = case.layout(),
|
||||
);
|
||||
bench!(
|
||||
"typeset",
|
||||
setup = |cache| cache.layout.clear(),
|
||||
setup = |ctx| ctx.layouts.clear(),
|
||||
code = case.typeset(),
|
||||
);
|
||||
bench!("layout-cached", case.layout());
|
||||
@ -77,28 +81,11 @@ fn benchmarks(c: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The context required for benchmarking a case.
|
||||
struct Context {
|
||||
loader: FsLoader,
|
||||
cache: Cache,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
fn new() -> Rc<RefCell<Self>> {
|
||||
let mut loader = FsLoader::new();
|
||||
loader.search_path(FONT_DIR);
|
||||
let cache = Cache::new(&loader);
|
||||
Rc::new(RefCell::new(Self { loader, cache }))
|
||||
}
|
||||
}
|
||||
|
||||
/// A test case with prepared intermediate results.
|
||||
struct Case {
|
||||
ctx: Rc<RefCell<Context>>,
|
||||
src_id: FileId,
|
||||
src: String,
|
||||
scope: Scope,
|
||||
state: State,
|
||||
ast: Rc<SyntaxTree>,
|
||||
module: Module,
|
||||
tree: LayoutTree,
|
||||
@ -108,20 +95,15 @@ struct Case {
|
||||
impl Case {
|
||||
fn new(src_id: FileId, src: String, ctx: Rc<RefCell<Context>>) -> Self {
|
||||
let mut borrowed = ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
let scope = typst::library::new();
|
||||
let state = typst::exec::State::default();
|
||||
let ast = Rc::new(parse(&src).output);
|
||||
let module = eval(loader, cache, src_id, Rc::clone(&ast), &scope).output;
|
||||
let tree = exec(&module.template, state.clone()).output;
|
||||
let frames = layout(loader, cache, &tree);
|
||||
let module = eval(&mut borrowed, src_id, Rc::clone(&ast)).output;
|
||||
let tree = exec(&mut borrowed, &module.template).output;
|
||||
let frames = layout(&mut borrowed, &tree);
|
||||
drop(borrowed);
|
||||
Self {
|
||||
ctx,
|
||||
src_id,
|
||||
src,
|
||||
scope,
|
||||
state,
|
||||
ast,
|
||||
module,
|
||||
tree,
|
||||
@ -135,31 +117,23 @@ impl Case {
|
||||
|
||||
fn eval(&self) -> Module {
|
||||
let mut borrowed = self.ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
let ast = Rc::clone(&self.ast);
|
||||
eval(loader, cache, self.src_id, ast, &self.scope).output
|
||||
eval(&mut borrowed, self.src_id, Rc::clone(&self.ast)).output
|
||||
}
|
||||
|
||||
fn exec(&self) -> LayoutTree {
|
||||
exec(&self.module.template, self.state.clone()).output
|
||||
exec(&mut self.ctx.borrow_mut(), &self.module.template).output
|
||||
}
|
||||
|
||||
fn layout(&self) -> Vec<Rc<Frame>> {
|
||||
let mut borrowed = self.ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
layout(loader, cache, &self.tree)
|
||||
layout(&mut self.ctx.borrow_mut(), &self.tree)
|
||||
}
|
||||
|
||||
fn typeset(&self) -> Vec<Rc<Frame>> {
|
||||
let mut borrowed = self.ctx.borrow_mut();
|
||||
let Context { loader, cache } = &mut *borrowed;
|
||||
let state = self.state.clone();
|
||||
typeset(loader, cache, self.src_id, &self.src, &self.scope, state).output
|
||||
self.ctx.borrow_mut().typeset(self.src_id, &self.src).output
|
||||
}
|
||||
|
||||
fn pdf(&self) -> Vec<u8> {
|
||||
let ctx = self.ctx.borrow();
|
||||
pdf(&ctx.cache, &self.frames)
|
||||
pdf(&self.ctx.borrow(), &self.frames)
|
||||
}
|
||||
}
|
||||
|
||||
|
30
src/cache.rs
30
src/cache.rs
@ -1,30 +0,0 @@
|
||||
//! Caching of compilation artifacts.
|
||||
|
||||
use crate::font::FontCache;
|
||||
use crate::image::ImageCache;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use crate::layout::LayoutCache;
|
||||
use crate::loading::Loader;
|
||||
|
||||
/// Caches compilation artifacts.
|
||||
pub struct Cache {
|
||||
/// Caches parsed font faces.
|
||||
pub font: FontCache,
|
||||
/// Caches decoded images.
|
||||
pub image: ImageCache,
|
||||
/// Caches layouting artifacts.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub layout: LayoutCache,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
/// Create a new, empty cache.
|
||||
pub fn new(loader: &dyn Loader) -> Self {
|
||||
Self {
|
||||
font: FontCache::new(loader),
|
||||
image: ImageCache::new(),
|
||||
#[cfg(feature = "layout-cache")]
|
||||
layout: LayoutCache::new(),
|
||||
}
|
||||
}
|
||||
}
|
@ -25,24 +25,19 @@ use std::mem;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::diag::{Diag, DiagSet, Pass};
|
||||
use crate::eco::EcoString;
|
||||
use crate::geom::{Angle, Fractional, Length, Relative};
|
||||
use crate::image::ImageCache;
|
||||
use crate::loading::{FileId, Loader};
|
||||
use crate::parse::parse;
|
||||
use crate::syntax::visit::Visit;
|
||||
use crate::syntax::*;
|
||||
use crate::Context;
|
||||
|
||||
/// Evaluate a parsed source file into a module.
|
||||
pub fn eval(
|
||||
loader: &mut dyn Loader,
|
||||
cache: &mut Cache,
|
||||
location: FileId,
|
||||
ast: Rc<SyntaxTree>,
|
||||
scope: &Scope,
|
||||
) -> Pass<Module> {
|
||||
let mut ctx = EvalContext::new(loader, cache, location, scope);
|
||||
pub fn eval(ctx: &mut Context, location: FileId, ast: Rc<SyntaxTree>) -> Pass<Module> {
|
||||
let mut ctx = EvalContext::new(ctx, location);
|
||||
let template = ast.eval(&mut ctx);
|
||||
let module = Module { scope: ctx.scopes.top, template };
|
||||
Pass::new(module, ctx.diags)
|
||||
@ -60,9 +55,9 @@ pub struct Module {
|
||||
/// The context for evaluation.
|
||||
pub struct EvalContext<'a> {
|
||||
/// The loader from which resources (files and images) are loaded.
|
||||
pub loader: &'a mut dyn Loader,
|
||||
/// A cache for loaded resources.
|
||||
pub cache: &'a mut Cache,
|
||||
pub loader: &'a dyn Loader,
|
||||
/// The cache for decoded images.
|
||||
pub images: &'a mut ImageCache,
|
||||
/// The active scopes.
|
||||
pub scopes: Scopes<'a>,
|
||||
/// Evaluation diagnostics.
|
||||
@ -74,17 +69,12 @@ pub struct EvalContext<'a> {
|
||||
}
|
||||
|
||||
impl<'a> EvalContext<'a> {
|
||||
/// Create a new evaluation context with a base scope.
|
||||
pub fn new(
|
||||
loader: &'a mut dyn Loader,
|
||||
cache: &'a mut Cache,
|
||||
location: FileId,
|
||||
scope: &'a Scope,
|
||||
) -> Self {
|
||||
/// Create a new evaluation context.
|
||||
pub fn new(ctx: &'a mut Context, location: FileId) -> Self {
|
||||
Self {
|
||||
loader,
|
||||
cache,
|
||||
scopes: Scopes::new(Some(scope)),
|
||||
loader: ctx.loader.as_ref(),
|
||||
images: &mut ctx.images,
|
||||
scopes: Scopes::new(Some(&ctx.std)),
|
||||
diags: DiagSet::new(),
|
||||
route: vec![location],
|
||||
modules: HashMap::new(),
|
||||
|
@ -10,6 +10,7 @@ use crate::layout::{
|
||||
LayoutNode, LayoutTree, PadNode, PageRun, ParChild, ParNode, StackChild, StackNode,
|
||||
};
|
||||
use crate::syntax::{Span, SyntaxTree};
|
||||
use crate::Context;
|
||||
|
||||
/// The context for execution.
|
||||
pub struct ExecContext {
|
||||
@ -28,13 +29,13 @@ pub struct ExecContext {
|
||||
|
||||
impl ExecContext {
|
||||
/// Create a new execution context with a base state.
|
||||
pub fn new(state: State) -> Self {
|
||||
pub fn new(ctx: &mut Context) -> Self {
|
||||
Self {
|
||||
state: ctx.state.clone(),
|
||||
diags: DiagSet::new(),
|
||||
tree: LayoutTree { runs: vec![] },
|
||||
page: Some(PageBuilder::new(&state, true)),
|
||||
stack: StackBuilder::new(&state),
|
||||
state,
|
||||
page: Some(PageBuilder::new(&ctx.state, true)),
|
||||
stack: StackBuilder::new(&ctx.state),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,10 +16,11 @@ use crate::geom::{Dir, Gen};
|
||||
use crate::layout::{LayoutTree, StackChild, StackNode};
|
||||
use crate::pretty::pretty;
|
||||
use crate::syntax::*;
|
||||
use crate::Context;
|
||||
|
||||
/// Execute a template to produce a layout tree.
|
||||
pub fn exec(template: &Template, state: State) -> Pass<LayoutTree> {
|
||||
let mut ctx = ExecContext::new(state);
|
||||
pub fn exec(ctx: &mut Context, template: &Template) -> Pass<LayoutTree> {
|
||||
let mut ctx = ExecContext::new(ctx);
|
||||
template.exec(&mut ctx);
|
||||
ctx.finish()
|
||||
}
|
||||
|
@ -13,12 +13,12 @@ use pdf_writer::{
|
||||
};
|
||||
use ttf_parser::{name_id, GlyphId};
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::color::Color;
|
||||
use crate::font::{Em, FaceId};
|
||||
use crate::font::{Em, FaceId, FontCache};
|
||||
use crate::geom::{self, Length, Size};
|
||||
use crate::image::{Image, ImageId};
|
||||
use crate::image::{Image, ImageCache, ImageId};
|
||||
use crate::layout::{Element, Frame, Geometry, Paint};
|
||||
use crate::Context;
|
||||
|
||||
/// Export a collection of frames into a PDF document.
|
||||
///
|
||||
@ -27,53 +27,55 @@ use crate::layout::{Element, Frame, Geometry, Paint};
|
||||
/// can be included in the PDF.
|
||||
///
|
||||
/// Returns the raw bytes making up the PDF document.
|
||||
pub fn pdf(cache: &Cache, frames: &[Rc<Frame>]) -> Vec<u8> {
|
||||
PdfExporter::new(cache, frames).write()
|
||||
pub fn pdf(ctx: &Context, frames: &[Rc<Frame>]) -> Vec<u8> {
|
||||
PdfExporter::new(ctx, frames).write()
|
||||
}
|
||||
|
||||
struct PdfExporter<'a> {
|
||||
writer: PdfWriter,
|
||||
frames: &'a [Rc<Frame>],
|
||||
cache: &'a Cache,
|
||||
fonts: &'a FontCache,
|
||||
font_map: Remapper<FaceId>,
|
||||
images: &'a ImageCache,
|
||||
image_map: Remapper<ImageId>,
|
||||
refs: Refs,
|
||||
fonts: Remapper<FaceId>,
|
||||
images: Remapper<ImageId>,
|
||||
}
|
||||
|
||||
impl<'a> PdfExporter<'a> {
|
||||
fn new(cache: &'a Cache, frames: &'a [Rc<Frame>]) -> Self {
|
||||
fn new(ctx: &'a Context, frames: &'a [Rc<Frame>]) -> Self {
|
||||
let mut writer = PdfWriter::new(1, 7);
|
||||
writer.set_indent(2);
|
||||
|
||||
let mut fonts = Remapper::new();
|
||||
let mut images = Remapper::new();
|
||||
let mut font_map = Remapper::new();
|
||||
let mut image_map = Remapper::new();
|
||||
let mut alpha_masks = 0;
|
||||
|
||||
for frame in frames {
|
||||
for (_, element) in frame.elements() {
|
||||
match *element {
|
||||
Element::Text(ref shaped) => fonts.insert(shaped.face_id),
|
||||
Element::Text(ref shaped) => font_map.insert(shaped.face_id),
|
||||
Element::Geometry(_, _) => {}
|
||||
Element::Image(id, _) => {
|
||||
let img = cache.image.get(id);
|
||||
let img = ctx.images.get(id);
|
||||
if img.buf.color().has_alpha() {
|
||||
alpha_masks += 1;
|
||||
}
|
||||
images.insert(id);
|
||||
image_map.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let refs = Refs::new(frames.len(), fonts.len(), images.len(), alpha_masks);
|
||||
let refs = Refs::new(frames.len(), font_map.len(), image_map.len(), alpha_masks);
|
||||
|
||||
Self {
|
||||
writer,
|
||||
frames,
|
||||
cache,
|
||||
fonts: &ctx.fonts,
|
||||
images: &ctx.images,
|
||||
refs,
|
||||
fonts,
|
||||
images,
|
||||
font_map,
|
||||
image_map,
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,7 +97,7 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
let mut resources = pages.resources();
|
||||
let mut fonts = resources.fonts();
|
||||
for (refs, f) in self.refs.fonts().zip(self.fonts.pdf_indices()) {
|
||||
for (refs, f) in self.refs.fonts().zip(self.font_map.pdf_indices()) {
|
||||
let name = format!("F{}", f);
|
||||
fonts.pair(Name(name.as_bytes()), refs.type0_font);
|
||||
}
|
||||
@ -103,7 +105,7 @@ impl<'a> PdfExporter<'a> {
|
||||
drop(fonts);
|
||||
|
||||
let mut images = resources.x_objects();
|
||||
for (id, im) in self.refs.images().zip(self.images.pdf_indices()) {
|
||||
for (id, im) in self.refs.images().zip(self.image_map.pdf_indices()) {
|
||||
let name = format!("Im{}", im);
|
||||
images.pair(Name(name.as_bytes()), id);
|
||||
}
|
||||
@ -163,7 +165,7 @@ impl<'a> PdfExporter<'a> {
|
||||
face = Some(shaped.face_id);
|
||||
size = shaped.size;
|
||||
|
||||
let name = format!("F{}", self.fonts.map(shaped.face_id));
|
||||
let name = format!("F{}", self.font_map.map(shaped.face_id));
|
||||
text.font(Name(name.as_bytes()), size.to_pt() as f32);
|
||||
}
|
||||
|
||||
@ -206,7 +208,7 @@ impl<'a> PdfExporter<'a> {
|
||||
}
|
||||
|
||||
Element::Image(id, Size { width, height }) => {
|
||||
let name = format!("Im{}", self.images.map(id));
|
||||
let name = format!("Im{}", self.image_map.map(id));
|
||||
let w = width.to_pt() as f32;
|
||||
let h = height.to_pt() as f32;
|
||||
|
||||
@ -222,8 +224,8 @@ impl<'a> PdfExporter<'a> {
|
||||
}
|
||||
|
||||
fn write_fonts(&mut self) {
|
||||
for (refs, face_id) in self.refs.fonts().zip(self.fonts.layout_indices()) {
|
||||
let face = self.cache.font.get(face_id);
|
||||
for (refs, face_id) in self.refs.fonts().zip(self.font_map.layout_indices()) {
|
||||
let face = self.fonts.get(face_id);
|
||||
let ttf = face.ttf();
|
||||
|
||||
let name = ttf
|
||||
@ -327,8 +329,8 @@ impl<'a> PdfExporter<'a> {
|
||||
fn write_images(&mut self) {
|
||||
let mut masks_seen = 0;
|
||||
|
||||
for (id, image_id) in self.refs.images().zip(self.images.layout_indices()) {
|
||||
let img = self.cache.image.get(image_id);
|
||||
for (id, image_id) in self.refs.images().zip(self.image_map.layout_indices()) {
|
||||
let img = self.images.get(image_id);
|
||||
let (width, height) = img.buf.dimensions();
|
||||
|
||||
// Add the primary image.
|
||||
|
15
src/font.rs
15
src/font.rs
@ -203,6 +203,7 @@ impl Add for Em {
|
||||
|
||||
/// Caches parsed font faces.
|
||||
pub struct FontCache {
|
||||
loader: Rc<dyn Loader>,
|
||||
faces: Vec<Option<Face>>,
|
||||
families: HashMap<String, Vec<FaceId>>,
|
||||
buffers: HashMap<FileId, Rc<Vec<u8>>>,
|
||||
@ -211,7 +212,7 @@ pub struct FontCache {
|
||||
|
||||
impl FontCache {
|
||||
/// Create a new, empty font cache.
|
||||
pub fn new(loader: &dyn Loader) -> Self {
|
||||
pub fn new(loader: Rc<dyn Loader>) -> Self {
|
||||
let mut faces = vec![];
|
||||
let mut families = HashMap::<String, Vec<FaceId>>::new();
|
||||
|
||||
@ -225,6 +226,7 @@ impl FontCache {
|
||||
}
|
||||
|
||||
Self {
|
||||
loader,
|
||||
faces,
|
||||
families,
|
||||
buffers: HashMap::new(),
|
||||
@ -234,15 +236,10 @@ impl FontCache {
|
||||
|
||||
/// Query for and load the font face from the given `family` that most
|
||||
/// closely matches the given `variant`.
|
||||
pub fn select(
|
||||
&mut self,
|
||||
loader: &mut dyn Loader,
|
||||
family: &str,
|
||||
variant: FontVariant,
|
||||
) -> Option<FaceId> {
|
||||
pub fn select(&mut self, family: &str, variant: FontVariant) -> Option<FaceId> {
|
||||
// Check whether a family with this name exists.
|
||||
let ids = self.families.get(family)?;
|
||||
let infos = loader.faces();
|
||||
let infos = self.loader.faces();
|
||||
|
||||
let mut best = None;
|
||||
let mut best_key = None;
|
||||
@ -284,7 +281,7 @@ impl FontCache {
|
||||
let buffer = match self.buffers.entry(file) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
let buffer = loader.load_file(file)?;
|
||||
let buffer = self.loader.load_file(file)?;
|
||||
entry.insert(Rc::new(buffer))
|
||||
}
|
||||
};
|
||||
|
17
src/image.rs
17
src/image.rs
@ -3,6 +3,7 @@
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::io::Cursor;
|
||||
use std::rc::Rc;
|
||||
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat};
|
||||
@ -53,25 +54,27 @@ impl Debug for Image {
|
||||
}
|
||||
|
||||
/// Caches decoded images.
|
||||
#[derive(Default)]
|
||||
pub struct ImageCache {
|
||||
/// Maps from file hashes to ids of decoded images.
|
||||
loader: Rc<dyn Loader>,
|
||||
images: HashMap<ImageId, Image>,
|
||||
/// Callback for loaded images.
|
||||
on_load: Option<Box<dyn Fn(ImageId, &Image)>>,
|
||||
}
|
||||
|
||||
impl ImageCache {
|
||||
/// Create a new, empty image cache.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
pub fn new(loader: Rc<dyn Loader>) -> Self {
|
||||
Self {
|
||||
loader,
|
||||
images: HashMap::new(),
|
||||
on_load: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load and decode an image file from a path.
|
||||
pub fn load(&mut self, loader: &mut dyn Loader, file: FileId) -> Option<ImageId> {
|
||||
pub fn load(&mut self, file: FileId) -> Option<ImageId> {
|
||||
let id = ImageId(file.into_raw());
|
||||
if let Entry::Vacant(entry) = self.images.entry(id) {
|
||||
let buffer = loader.load_file(file)?;
|
||||
let buffer = self.loader.load_file(file)?;
|
||||
let image = Image::parse(&buffer)?;
|
||||
if let Some(callback) = &self.on_load {
|
||||
callback(id, &image);
|
||||
|
@ -28,7 +28,7 @@ impl Layout for ImageNode {
|
||||
let width = self.width.map(|w| w.resolve(base.width));
|
||||
let height = self.height.map(|w| w.resolve(base.height));
|
||||
|
||||
let dimensions = ctx.cache.image.get(self.id).buf.dimensions();
|
||||
let dimensions = ctx.images.get(self.id).buf.dimensions();
|
||||
let pixel_width = dimensions.0 as f64;
|
||||
let pixel_height = dimensions.1 as f64;
|
||||
let pixel_ratio = pixel_width / pixel_height;
|
||||
|
@ -32,22 +32,16 @@ use std::rc::Rc;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use fxhash::FxHasher64;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::font::FontCache;
|
||||
use crate::geom::*;
|
||||
use crate::image::ImageCache;
|
||||
use crate::loading::Loader;
|
||||
use crate::Context;
|
||||
|
||||
/// Layout a tree into a collection of frames.
|
||||
pub fn layout(
|
||||
loader: &mut dyn Loader,
|
||||
cache: &mut Cache,
|
||||
tree: &LayoutTree,
|
||||
) -> Vec<Rc<Frame>> {
|
||||
tree.layout(&mut LayoutContext {
|
||||
loader,
|
||||
cache,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
level: 0,
|
||||
})
|
||||
pub fn layout(ctx: &mut Context, tree: &LayoutTree) -> Vec<Rc<Frame>> {
|
||||
let mut ctx = LayoutContext::new(ctx);
|
||||
tree.layout(&mut ctx)
|
||||
}
|
||||
|
||||
/// A tree of layout nodes.
|
||||
@ -129,15 +123,15 @@ impl Layout for LayoutNode {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
{
|
||||
ctx.level += 1;
|
||||
let frames =
|
||||
ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| {
|
||||
let frames = self.node.layout(ctx, regions);
|
||||
ctx.cache.layout.insert(self.hash, frames.clone(), ctx.level - 1);
|
||||
frames
|
||||
});
|
||||
let frames = ctx.layouts.get(self.hash, regions.clone()).unwrap_or_else(|| {
|
||||
let frames = self.node.layout(ctx, regions);
|
||||
ctx.layouts.insert(self.hash, frames.clone(), ctx.level - 1);
|
||||
frames
|
||||
});
|
||||
ctx.level -= 1;
|
||||
frames
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "layout-cache"))]
|
||||
self.node.layout(ctx, regions)
|
||||
}
|
||||
@ -214,14 +208,34 @@ pub trait Layout {
|
||||
/// The context for layouting.
|
||||
pub struct LayoutContext<'a> {
|
||||
/// The loader from which fonts are loaded.
|
||||
pub loader: &'a mut dyn Loader,
|
||||
/// A cache for loaded fonts and artifacts from past layouting.
|
||||
pub cache: &'a mut Cache,
|
||||
pub loader: &'a dyn Loader,
|
||||
/// The cache for parsed font faces.
|
||||
pub fonts: &'a mut FontCache,
|
||||
/// The cache for decoded imges.
|
||||
pub images: &'a mut ImageCache,
|
||||
/// The cache for layouting artifacts.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub layouts: &'a mut LayoutCache,
|
||||
/// How deeply nested the current layout tree position is.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub level: usize,
|
||||
}
|
||||
|
||||
impl<'a> LayoutContext<'a> {
|
||||
/// Create a new layout context.
|
||||
pub fn new(ctx: &'a mut Context) -> Self {
|
||||
Self {
|
||||
loader: ctx.loader.as_ref(),
|
||||
fonts: &mut ctx.fonts,
|
||||
images: &mut ctx.images,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
layouts: &mut ctx.layouts,
|
||||
#[cfg(feature = "layout-cache")]
|
||||
level: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A sequence of regions to layout into.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct Regions {
|
||||
|
@ -235,7 +235,7 @@ fn shape_segment<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(id) = ctx.cache.font.select(ctx.loader, family, variant) {
|
||||
if let Some(id) = ctx.fonts.select(family, variant) {
|
||||
break (id, true);
|
||||
}
|
||||
}
|
||||
@ -262,7 +262,7 @@ fn shape_segment<'a>(
|
||||
});
|
||||
|
||||
// Shape!
|
||||
let mut face = ctx.cache.font.get(face_id);
|
||||
let mut face = ctx.fonts.get(face_id);
|
||||
let buffer = rustybuzz::shape(face.ttf(), &[], buffer);
|
||||
let infos = buffer.glyph_infos();
|
||||
let pos = buffer.glyph_positions();
|
||||
@ -337,7 +337,7 @@ fn shape_segment<'a>(
|
||||
first_face,
|
||||
);
|
||||
|
||||
face = ctx.cache.font.get(face_id);
|
||||
face = ctx.fonts.get(face_id);
|
||||
}
|
||||
|
||||
i += 1;
|
||||
@ -351,8 +351,6 @@ fn measure(
|
||||
glyphs: &[ShapedGlyph],
|
||||
state: &FontState,
|
||||
) -> (Size, Length) {
|
||||
let cache = &mut ctx.cache.font;
|
||||
|
||||
let mut width = Length::zero();
|
||||
let mut top = Length::zero();
|
||||
let mut bottom = Length::zero();
|
||||
@ -365,14 +363,14 @@ fn measure(
|
||||
// When there are no glyphs, we just use the vertical metrics of the
|
||||
// first available font.
|
||||
for family in state.families.iter() {
|
||||
if let Some(face_id) = cache.select(ctx.loader, family, state.variant) {
|
||||
expand_vertical(cache.get(face_id));
|
||||
if let Some(face_id) = ctx.fonts.select(family, state.variant) {
|
||||
expand_vertical(ctx.fonts.get(face_id));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (face_id, group) in glyphs.group_by_key(|g| g.face_id) {
|
||||
let face = cache.get(face_id);
|
||||
let face = ctx.fonts.get(face_id);
|
||||
expand_vertical(face);
|
||||
|
||||
for glyph in group {
|
||||
@ -394,7 +392,7 @@ fn decorate(
|
||||
state: &FontState,
|
||||
) {
|
||||
let mut apply = |substate: &LineState, metrics: fn(&Face) -> &LineMetrics| {
|
||||
let metrics = metrics(&ctx.cache.font.get(face_id));
|
||||
let metrics = metrics(ctx.fonts.get(face_id));
|
||||
|
||||
let stroke = substate.stroke.unwrap_or(state.fill);
|
||||
|
||||
|
131
src/lib.rs
131
src/lib.rs
@ -32,7 +32,6 @@
|
||||
pub mod diag;
|
||||
#[macro_use]
|
||||
pub mod eval;
|
||||
pub mod cache;
|
||||
pub mod color;
|
||||
pub mod eco;
|
||||
pub mod exec;
|
||||
@ -51,46 +50,106 @@ pub mod util;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::diag::Pass;
|
||||
use crate::eval::Scope;
|
||||
use crate::exec::State;
|
||||
use crate::font::FontCache;
|
||||
use crate::image::ImageCache;
|
||||
use crate::layout::Frame;
|
||||
#[cfg(feature = "layout-cache")]
|
||||
use crate::layout::LayoutCache;
|
||||
use crate::loading::{FileId, Loader};
|
||||
|
||||
/// Process source code directly into a collection of layouted frames.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - The `loader` is used to load fonts, images and other source files.
|
||||
/// - The `cache` stores things that are reusable across several compilations
|
||||
/// like loaded fonts, decoded images and layouting artifacts.
|
||||
/// - The `location` is the file id of the source file and is used to resolve
|
||||
/// relative paths (for importing and image loading).
|
||||
/// - The `src` is the _Typst_ source code to typeset.
|
||||
/// - The `scope` contains definitions that are available everywhere, typically
|
||||
/// the standard library.
|
||||
/// - The `state` defines initial properties for page size, font selection and
|
||||
/// so on.
|
||||
///
|
||||
/// # Return value
|
||||
/// Returns a vector of frames representing individual pages alongside
|
||||
/// diagnostic information (errors and warnings).
|
||||
pub fn typeset(
|
||||
loader: &mut dyn Loader,
|
||||
cache: &mut Cache,
|
||||
location: FileId,
|
||||
src: &str,
|
||||
scope: &Scope,
|
||||
/// The core context which holds the loader, configuration and cached artifacts.
|
||||
pub struct Context {
|
||||
/// The loader the context was created with.
|
||||
pub loader: Rc<dyn Loader>,
|
||||
/// Caches parsed font faces.
|
||||
pub fonts: FontCache,
|
||||
/// Caches decoded images.
|
||||
pub images: ImageCache,
|
||||
/// Caches layouting artifacts.
|
||||
#[cfg(feature = "layout-cache")]
|
||||
pub layouts: LayoutCache,
|
||||
/// The standard library scope.
|
||||
std: Scope,
|
||||
/// The default state.
|
||||
state: State,
|
||||
) -> Pass<Vec<Rc<Frame>>> {
|
||||
let ast = parse::parse(src);
|
||||
let module = eval::eval(loader, cache, location, Rc::new(ast.output), scope);
|
||||
let tree = exec::exec(&module.output.template, state);
|
||||
let frames = layout::layout(loader, cache, &tree.output);
|
||||
|
||||
let mut diags = ast.diags;
|
||||
diags.extend(module.diags);
|
||||
diags.extend(tree.diags);
|
||||
|
||||
Pass::new(frames, diags)
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Create a new context with the default settings.
|
||||
pub fn new(loader: Rc<dyn Loader>) -> Self {
|
||||
Self::builder().build(loader)
|
||||
}
|
||||
|
||||
/// Create a new context with advanced settings.
|
||||
pub fn builder() -> ContextBuilder {
|
||||
ContextBuilder::default()
|
||||
}
|
||||
|
||||
/// Garbage-collect caches.
|
||||
pub fn turnaround(&mut self) {
|
||||
#[cfg(feature = "layout-cache")]
|
||||
self.layouts.turnaround();
|
||||
}
|
||||
|
||||
/// Typeset a source file into a collection of layouted frames.
|
||||
///
|
||||
/// The `file` is the file id of the source file and is used to resolve
|
||||
/// relative paths (for importing and image loading).
|
||||
///
|
||||
/// Returns a vector of frames representing individual pages alongside
|
||||
/// diagnostic information (errors and warnings).
|
||||
pub fn typeset(&mut self, file: FileId, src: &str) -> Pass<Vec<Rc<Frame>>> {
|
||||
let ast = parse::parse(src);
|
||||
let module = eval::eval(self, file, Rc::new(ast.output));
|
||||
let tree = exec::exec(self, &module.output.template);
|
||||
let frames = layout::layout(self, &tree.output);
|
||||
|
||||
let mut diags = ast.diags;
|
||||
diags.extend(module.diags);
|
||||
diags.extend(tree.diags);
|
||||
|
||||
Pass::new(frames, diags)
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder for a [`Context`].
|
||||
///
|
||||
/// This struct is created by [`Context::builder`].
|
||||
#[derive(Default)]
|
||||
pub struct ContextBuilder {
|
||||
std: Option<Scope>,
|
||||
state: Option<State>,
|
||||
}
|
||||
|
||||
impl ContextBuilder {
|
||||
/// The scope containing definitions that are available everywhere,
|
||||
/// (the standard library).
|
||||
pub fn std(mut self, std: Scope) -> Self {
|
||||
self.std = Some(std);
|
||||
self
|
||||
}
|
||||
|
||||
/// The `state` defining initial properties for page size, font selection
|
||||
/// and so on.
|
||||
pub fn state(mut self, state: State) -> Self {
|
||||
self.state = Some(state);
|
||||
self
|
||||
}
|
||||
|
||||
/// Finish building the context by providing the `loader` used to load
|
||||
/// fonts, images, source files and other resources.
|
||||
pub fn build(self, loader: Rc<dyn Loader>) -> Context {
|
||||
Context {
|
||||
loader: Rc::clone(&loader),
|
||||
fonts: FontCache::new(Rc::clone(&loader)),
|
||||
images: ImageCache::new(loader),
|
||||
#[cfg(feature = "layout-cache")]
|
||||
layouts: LayoutCache::new(),
|
||||
std: self.std.unwrap_or(library::new()),
|
||||
state: self.state.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
let mut node = None;
|
||||
if let Some(path) = &path {
|
||||
if let Some(file) = ctx.resolve(&path.v, path.span) {
|
||||
if let Some(id) = ctx.cache.image.load(ctx.loader, file) {
|
||||
if let Some(id) = ctx.images.load(file) {
|
||||
node = Some(ImageNode { id, width, height });
|
||||
} else {
|
||||
ctx.diag(error!(path.span, "failed to load image"));
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::io;
|
||||
@ -18,23 +19,23 @@ use crate::util::PathExt;
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct FsLoader {
|
||||
faces: Vec<FaceInfo>,
|
||||
paths: HashMap<FileId, PathBuf>,
|
||||
paths: RefCell<HashMap<FileId, PathBuf>>,
|
||||
}
|
||||
|
||||
impl FsLoader {
|
||||
/// Create a new loader without any fonts.
|
||||
pub fn new() -> Self {
|
||||
Self { faces: vec![], paths: HashMap::new() }
|
||||
Self { faces: vec![], paths: RefCell::default() }
|
||||
}
|
||||
|
||||
/// Resolve a file id for a path.
|
||||
pub fn resolve_path(&mut self, path: &Path) -> io::Result<FileId> {
|
||||
pub fn resolve_path(&self, path: &Path) -> io::Result<FileId> {
|
||||
let file = File::open(path)?;
|
||||
let meta = file.metadata()?;
|
||||
if meta.is_file() {
|
||||
let handle = Handle::from_file(file)?;
|
||||
let id = FileId(fxhash::hash64(&handle));
|
||||
self.paths.insert(id, path.normalize());
|
||||
self.paths.borrow_mut().insert(id, path.normalize());
|
||||
Ok(id)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "not a file"))
|
||||
@ -165,14 +166,13 @@ impl Loader for FsLoader {
|
||||
&self.faces
|
||||
}
|
||||
|
||||
fn resolve_from(&mut self, base: FileId, path: &Path) -> Option<FileId> {
|
||||
let dir = self.paths[&base].parent()?;
|
||||
let full = dir.join(path);
|
||||
self.resolve_path(&full).ok()
|
||||
fn resolve_from(&self, base: FileId, path: &Path) -> Option<FileId> {
|
||||
let full = self.paths.borrow()[&base].parent()?.join(path);
|
||||
self.resolve(&full).ok()
|
||||
}
|
||||
|
||||
fn load_file(&mut self, id: FileId) -> Option<Vec<u8>> {
|
||||
fs::read(&self.paths[&id]).ok()
|
||||
fn load_file(&self, id: FileId) -> Option<Vec<u8>> {
|
||||
fs::read(&self.paths.borrow()[&id]).ok()
|
||||
}
|
||||
}
|
||||
|
||||
@ -185,7 +185,8 @@ mod tests {
|
||||
let mut loader = FsLoader::new();
|
||||
loader.search_path("fonts");
|
||||
|
||||
let mut paths: Vec<_> = loader.paths.values().collect();
|
||||
let map = loader.paths.borrow();
|
||||
let mut paths: Vec<_> = map.values().collect();
|
||||
paths.sort();
|
||||
|
||||
assert_eq!(paths, [
|
||||
|
@ -21,10 +21,10 @@ pub trait Loader {
|
||||
///
|
||||
/// This should return the same id for all paths pointing to the same file
|
||||
/// and `None` if the file does not exist.
|
||||
fn resolve_from(&mut self, base: FileId, path: &Path) -> Option<FileId>;
|
||||
fn resolve_from(&self, base: FileId, path: &Path) -> Option<FileId>;
|
||||
|
||||
/// Load a file by id.
|
||||
fn load_file(&mut self, id: FileId) -> Option<Vec<u8>>;
|
||||
fn load_file(&self, id: FileId) -> Option<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// A file id that can be [resolved](Loader::resolve_from) from a path.
|
||||
@ -53,11 +53,11 @@ impl Loader for BlankLoader {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn resolve_from(&mut self, _: FileId, _: &Path) -> Option<FileId> {
|
||||
fn resolve_from(&self, _: FileId, _: &Path) -> Option<FileId> {
|
||||
None
|
||||
}
|
||||
|
||||
fn load_file(&mut self, _: FileId) -> Option<Vec<u8>> {
|
||||
fn load_file(&self, _: FileId) -> Option<Vec<u8>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
25
src/main.rs
25
src/main.rs
@ -1,5 +1,6 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::rc::Rc;
|
||||
|
||||
use anyhow::{anyhow, bail, Context};
|
||||
use same_file::is_same_file;
|
||||
@ -11,11 +12,6 @@ fn main() -> anyhow::Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create a loader for fonts and files.
|
||||
let mut loader = typst::loading::FsLoader::new();
|
||||
loader.search_path("fonts");
|
||||
loader.search_system();
|
||||
|
||||
// Determine source and destination path.
|
||||
let src_path = Path::new(&args[1]);
|
||||
let dest_path = if let Some(arg) = args.get(2) {
|
||||
@ -33,18 +29,19 @@ fn main() -> anyhow::Result<()> {
|
||||
bail!("source and destination files are the same");
|
||||
}
|
||||
|
||||
// Resolve the file id of the source file.
|
||||
let src_id = loader.resolve_path(src_path).context("source file not found")?;
|
||||
// Create a loader for fonts and files.
|
||||
let mut loader = typst::loading::FsLoader::new();
|
||||
loader.search_path("fonts");
|
||||
loader.search_system();
|
||||
|
||||
// Read the source.
|
||||
// Resolve the file id of the source file and read the file.
|
||||
let src_id = loader.resolve_path(src_path).context("source file not found")?;
|
||||
let src = fs::read_to_string(&src_path)
|
||||
.map_err(|_| anyhow!("failed to read source file"))?;
|
||||
|
||||
// Compile.
|
||||
let mut cache = typst::cache::Cache::new(&loader);
|
||||
let scope = typst::library::new();
|
||||
let state = typst::exec::State::default();
|
||||
let pass = typst::typeset(&mut loader, &mut cache, src_id, &src, &scope, state);
|
||||
// Typeset.
|
||||
let mut ctx = typst::Context::new(Rc::new(loader));
|
||||
let pass = ctx.typeset(src_id, &src);
|
||||
|
||||
// Print diagnostics.
|
||||
let map = typst::parse::LineMap::new(&src);
|
||||
@ -62,7 +59,7 @@ fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
// Export the PDF.
|
||||
let buffer = typst::export::pdf(&cache, &pass.output);
|
||||
let buffer = typst::export::pdf(&ctx, &pass.output);
|
||||
fs::write(&dest_path, buffer).context("failed to write PDF file")?;
|
||||
|
||||
Ok(())
|
||||
|
@ -10,7 +10,6 @@ use tiny_skia as sk;
|
||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use typst::cache::Cache;
|
||||
use typst::color::Color;
|
||||
use typst::diag::{Diag, DiagSet, Level};
|
||||
use typst::eval::{eval, Scope, Value};
|
||||
@ -21,6 +20,7 @@ use typst::layout::{layout, Element, Frame, Geometry, Paint, Text};
|
||||
use typst::loading::{FileId, FsLoader};
|
||||
use typst::parse::{parse, LineMap, Scanner};
|
||||
use typst::syntax::{Location, Pos};
|
||||
use typst::Context;
|
||||
|
||||
const TYP_DIR: &str = "./typ";
|
||||
const REF_DIR: &str = "./ref";
|
||||
@ -57,10 +57,20 @@ fn main() {
|
||||
println!("Running {} tests", len);
|
||||
}
|
||||
|
||||
let mut loader = typst::loading::FsLoader::new();
|
||||
loader.search_path(FONT_DIR);
|
||||
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||
// large and fit them to match their content.
|
||||
let mut state = State::default();
|
||||
state.page.size = Size::new(Length::pt(120.0), Length::inf());
|
||||
state.page.margins = Sides::splat(Some(Length::pt(10.0).into()));
|
||||
|
||||
let mut cache = typst::cache::Cache::new(&loader);
|
||||
// Create a file system loader.
|
||||
let loader = {
|
||||
let mut loader = typst::loading::FsLoader::new();
|
||||
loader.search_path(FONT_DIR);
|
||||
Rc::new(loader)
|
||||
};
|
||||
|
||||
let mut ctx = typst::Context::builder().state(state).build(loader.clone());
|
||||
|
||||
let mut ok = true;
|
||||
for src_path in filtered {
|
||||
@ -71,8 +81,8 @@ fn main() {
|
||||
args.pdf.then(|| Path::new(PDF_DIR).join(path).with_extension("pdf"));
|
||||
|
||||
ok &= test(
|
||||
&mut loader,
|
||||
&mut cache,
|
||||
loader.as_ref(),
|
||||
&mut ctx,
|
||||
&src_path,
|
||||
&png_path,
|
||||
&ref_path,
|
||||
@ -125,8 +135,8 @@ struct Panic {
|
||||
}
|
||||
|
||||
fn test(
|
||||
loader: &mut FsLoader,
|
||||
cache: &mut Cache,
|
||||
loader: &FsLoader,
|
||||
ctx: &mut Context,
|
||||
src_path: &Path,
|
||||
png_path: &Path,
|
||||
ref_path: &Path,
|
||||
@ -160,7 +170,7 @@ fn test(
|
||||
}
|
||||
} else {
|
||||
let (part_ok, compare_here, part_frames) =
|
||||
test_part(loader, cache, src_id, part, i, compare_ref, lines);
|
||||
test_part(ctx, src_id, part, i, compare_ref, lines);
|
||||
ok &= part_ok;
|
||||
compare_ever |= compare_here;
|
||||
frames.extend(part_frames);
|
||||
@ -171,12 +181,12 @@ fn test(
|
||||
|
||||
if compare_ever {
|
||||
if let Some(pdf_path) = pdf_path {
|
||||
let pdf_data = typst::export::pdf(cache, &frames);
|
||||
let pdf_data = typst::export::pdf(ctx, &frames);
|
||||
fs::create_dir_all(&pdf_path.parent().unwrap()).unwrap();
|
||||
fs::write(pdf_path, pdf_data).unwrap();
|
||||
}
|
||||
|
||||
let canvas = draw(&cache, &frames, 2.0);
|
||||
let canvas = draw(ctx, &frames, 2.0);
|
||||
fs::create_dir_all(&png_path.parent().unwrap()).unwrap();
|
||||
canvas.save_png(png_path).unwrap();
|
||||
|
||||
@ -199,8 +209,7 @@ fn test(
|
||||
}
|
||||
|
||||
fn test_part(
|
||||
loader: &mut FsLoader,
|
||||
cache: &mut Cache,
|
||||
ctx: &mut Context,
|
||||
src_id: FileId,
|
||||
src: &str,
|
||||
i: usize,
|
||||
@ -216,16 +225,10 @@ fn test_part(
|
||||
let panics = Rc::new(RefCell::new(vec![]));
|
||||
register_helpers(&mut scope, Rc::clone(&panics));
|
||||
|
||||
// We want to have "unbounded" pages, so we allow them to be infinitely
|
||||
// large and fit them to match their content.
|
||||
let mut state = State::default();
|
||||
state.page.size = Size::new(Length::pt(120.0), Length::inf());
|
||||
state.page.margins = Sides::splat(Some(Length::pt(10.0).into()));
|
||||
|
||||
let parsed = parse(src);
|
||||
let evaluated = eval(loader, cache, src_id, Rc::new(parsed.output), &scope);
|
||||
let executed = exec(&evaluated.output.template, state.clone());
|
||||
let mut layouted = layout(loader, cache, &executed.output);
|
||||
let evaluated = eval(ctx, src_id, Rc::new(parsed.output));
|
||||
let executed = exec(ctx, &evaluated.output.template);
|
||||
let mut layouted = layout(ctx, &executed.output);
|
||||
|
||||
let mut diags = parsed.diags;
|
||||
diags.extend(evaluated.diags);
|
||||
@ -266,20 +269,20 @@ fn test_part(
|
||||
|
||||
#[cfg(feature = "layout-cache")]
|
||||
{
|
||||
let reference_cache = cache.layout.clone();
|
||||
for level in 0 .. reference_cache.levels() {
|
||||
cache.layout = reference_cache.clone();
|
||||
cache.layout.retain(|x| x == level);
|
||||
if cache.layout.frames.is_empty() {
|
||||
let reference = ctx.layouts.clone();
|
||||
for level in 0 .. reference.levels() {
|
||||
ctx.layouts = reference.clone();
|
||||
ctx.layouts.retain(|x| x == level);
|
||||
if ctx.layouts.frames.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
cache.layout.turnaround();
|
||||
ctx.layouts.turnaround();
|
||||
|
||||
let cached_result = layout(loader, cache, &executed.output);
|
||||
let cached_result = layout(ctx, &executed.output);
|
||||
|
||||
let misses = cache
|
||||
.layout
|
||||
let misses = ctx
|
||||
.layouts
|
||||
.frames
|
||||
.iter()
|
||||
.flat_map(|(_, e)| e)
|
||||
@ -303,8 +306,8 @@ fn test_part(
|
||||
}
|
||||
}
|
||||
|
||||
cache.layout = reference_cache;
|
||||
cache.layout.turnaround();
|
||||
ctx.layouts = reference;
|
||||
ctx.layouts.turnaround();
|
||||
}
|
||||
|
||||
if !compare_ref {
|
||||
@ -391,7 +394,7 @@ fn print_diag(diag: &Diag, map: &LineMap, lines: u32) {
|
||||
println!("{}: {}-{}: {}", diag.level, start, end, diag.message);
|
||||
}
|
||||
|
||||
fn draw(cache: &Cache, frames: &[Rc<Frame>], dpi: f32) -> sk::Pixmap {
|
||||
fn draw(ctx: &Context, frames: &[Rc<Frame>], dpi: f32) -> sk::Pixmap {
|
||||
let pad = Length::pt(5.0);
|
||||
|
||||
let height = pad + frames.iter().map(|l| l.size.height + pad).sum::<Length>();
|
||||
@ -434,13 +437,13 @@ fn draw(cache: &Cache, frames: &[Rc<Frame>], dpi: f32) -> sk::Pixmap {
|
||||
let ts = ts.pre_translate(x, y);
|
||||
match *element {
|
||||
Element::Text(ref text) => {
|
||||
draw_text(&mut canvas, ts, cache, text);
|
||||
draw_text(&mut canvas, ts, ctx, text);
|
||||
}
|
||||
Element::Geometry(ref geometry, paint) => {
|
||||
draw_geometry(&mut canvas, ts, geometry, paint);
|
||||
}
|
||||
Element::Image(id, size) => {
|
||||
draw_image(&mut canvas, ts, cache, id, size);
|
||||
draw_image(&mut canvas, ts, ctx, id, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -451,8 +454,8 @@ fn draw(cache: &Cache, frames: &[Rc<Frame>], dpi: f32) -> sk::Pixmap {
|
||||
canvas
|
||||
}
|
||||
|
||||
fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, cache: &Cache, text: &Text) {
|
||||
let ttf = cache.font.get(text.face_id).ttf();
|
||||
fn draw_text(canvas: &mut sk::Pixmap, ts: sk::Transform, ctx: &Context, text: &Text) {
|
||||
let ttf = ctx.fonts.get(text.face_id).ttf();
|
||||
let mut x = 0.0;
|
||||
|
||||
for glyph in &text.glyphs {
|
||||
@ -540,11 +543,11 @@ fn draw_geometry(
|
||||
fn draw_image(
|
||||
canvas: &mut sk::Pixmap,
|
||||
ts: sk::Transform,
|
||||
cache: &Cache,
|
||||
ctx: &Context,
|
||||
id: ImageId,
|
||||
size: Size,
|
||||
) {
|
||||
let img = cache.image.get(id);
|
||||
let img = ctx.images.get(id);
|
||||
|
||||
let mut pixmap = sk::Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
|
||||
for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user