mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Basic environment and resource loader 🏞
This commit is contained in:
parent
bc997b7c33
commit
475ca7a62e
@ -4,6 +4,7 @@ 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 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::FontLoader;
|
||||||
@ -25,23 +26,25 @@ fn benchmarks(c: &mut Criterion) {
|
|||||||
index.search_dir(FONT_DIR);
|
index.search_dir(FONT_DIR);
|
||||||
|
|
||||||
let (files, descriptors) = index.into_vecs();
|
let (files, descriptors) = index.into_vecs();
|
||||||
let loader = Rc::new(RefCell::new(FontLoader::new(
|
let env = Rc::new(RefCell::new(Env {
|
||||||
Box::new(FsSource::new(files)),
|
fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors),
|
||||||
descriptors,
|
resources: ResourceLoader::new(),
|
||||||
)));
|
}));
|
||||||
|
|
||||||
// Prepare intermediate results and run warm.
|
// Prepare intermediate results and run warm.
|
||||||
let state = State::default();
|
let state = State::default();
|
||||||
let tree = parse(COMA).output;
|
let tree = parse(COMA).output;
|
||||||
let document = eval(&tree, state.clone()).output;
|
let document = eval(&tree, Rc::clone(&env), state.clone()).output;
|
||||||
let layouts = layout(&document, Rc::clone(&loader));
|
let layouts = layout(&document, Rc::clone(&env));
|
||||||
|
|
||||||
// Bench!
|
// Bench!
|
||||||
bench!("parse-coma": parse(COMA));
|
bench!("parse-coma": parse(COMA));
|
||||||
bench!("eval-coma": eval(&tree, state.clone()));
|
bench!("eval-coma": eval(&tree, Rc::clone(&env), state.clone()));
|
||||||
bench!("layout-coma": layout(&document, Rc::clone(&loader)));
|
bench!("layout-coma": layout(&document, Rc::clone(&env)));
|
||||||
bench!("typeset-coma": typeset(COMA, state.clone(), Rc::clone(&loader)));
|
bench!("typeset-coma": typeset(COMA, Rc::clone(&env), state.clone()));
|
||||||
bench!("export-pdf-coma": pdf::export(&layouts, &loader.borrow()));
|
|
||||||
|
let env = env.borrow();
|
||||||
|
bench!("export-pdf-coma": pdf::export(&layouts, &env));
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, benchmarks);
|
criterion_group!(benches, benchmarks);
|
||||||
|
75
src/env.rs
Normal file
75
src/env.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//! Environment interactions.
|
||||||
|
|
||||||
|
use std::any::Any;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::font::FontLoader;
|
||||||
|
|
||||||
|
/// A reference-counted shared environment.
|
||||||
|
pub type SharedEnv = Rc<RefCell<Env>>;
|
||||||
|
|
||||||
|
/// Encapsulates all environment dependencies (fonts, resources).
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Env {
|
||||||
|
/// Loads fonts from a dynamic font source.
|
||||||
|
pub fonts: FontLoader,
|
||||||
|
/// Loads resource from the file system.
|
||||||
|
pub resources: ResourceLoader,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads resource from the file system.
|
||||||
|
pub struct ResourceLoader {
|
||||||
|
paths: HashMap<PathBuf, ResourceId>,
|
||||||
|
entries: Vec<Box<dyn Any>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A unique identifier for a resource.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub struct ResourceId(usize);
|
||||||
|
|
||||||
|
impl ResourceLoader {
|
||||||
|
/// Create a new resource loader.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { paths: HashMap::new(), entries: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a resource from a path.
|
||||||
|
pub fn load<R: 'static>(
|
||||||
|
&mut self,
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
parse: impl FnOnce(Vec<u8>) -> Option<R>,
|
||||||
|
) -> Option<(ResourceId, &R)> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let id = match self.paths.entry(path.to_owned()) {
|
||||||
|
Entry::Occupied(entry) => *entry.get(),
|
||||||
|
Entry::Vacant(entry) => {
|
||||||
|
let id = *entry.insert(ResourceId(self.entries.len()));
|
||||||
|
let data = fs::read(path).ok()?;
|
||||||
|
let resource = parse(data)?;
|
||||||
|
self.entries.push(Box::new(resource));
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((id, self.get_loaded(id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a previously loaded resource by its id.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This panics if no resource with this id was loaded.
|
||||||
|
pub fn get_loaded<R: 'static>(&self, id: ResourceId) -> &R {
|
||||||
|
self.entries[id.0].downcast_ref().expect("bad resource type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for ResourceLoader {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.debug_set().entries(self.paths.keys()).finish()
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ use fontdock::FontStyle;
|
|||||||
|
|
||||||
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::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size};
|
use crate::geom::{BoxAlign, Dir, Flow, Gen, Length, Linear, Relative, Sides, Size};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
|
Document, Expansion, LayoutNode, Pad, Pages, Par, Softness, Spacing, Stack, Text,
|
||||||
@ -30,10 +31,10 @@ use crate::syntax::*;
|
|||||||
|
|
||||||
/// Evaluate a syntax tree into a document.
|
/// Evaluate a syntax tree into a document.
|
||||||
///
|
///
|
||||||
/// The given `state` the base state that may be updated over the course of
|
/// The given `state` is the base state that may be updated over the course of
|
||||||
/// evaluation.
|
/// evaluation.
|
||||||
pub fn eval(tree: &SynTree, state: State) -> Pass<Document> {
|
pub fn eval(tree: &SynTree, env: SharedEnv, state: State) -> Pass<Document> {
|
||||||
let mut ctx = EvalContext::new(state);
|
let mut ctx = EvalContext::new(env, state);
|
||||||
ctx.start_page_group(false);
|
ctx.start_page_group(false);
|
||||||
tree.eval(&mut ctx);
|
tree.eval(&mut ctx);
|
||||||
ctx.end_page_group();
|
ctx.end_page_group();
|
||||||
@ -43,6 +44,8 @@ pub fn eval(tree: &SynTree, state: State) -> Pass<Document> {
|
|||||||
/// The context for evaluation.
|
/// The context for evaluation.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EvalContext {
|
pub struct EvalContext {
|
||||||
|
/// The environment from which resources are gathered.
|
||||||
|
pub env: SharedEnv,
|
||||||
/// The active evaluation state.
|
/// The active evaluation state.
|
||||||
pub state: State,
|
pub state: State,
|
||||||
/// The accumulated feedback.
|
/// The accumulated feedback.
|
||||||
@ -62,8 +65,9 @@ pub struct EvalContext {
|
|||||||
|
|
||||||
impl EvalContext {
|
impl EvalContext {
|
||||||
/// Create a new evaluation context with a base state.
|
/// Create a new evaluation context with a base state.
|
||||||
pub fn new(state: State) -> Self {
|
pub fn new(env: SharedEnv, state: State) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
env,
|
||||||
state,
|
state,
|
||||||
groups: vec![],
|
groups: vec![],
|
||||||
inner: vec![],
|
inner: vec![],
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use fontdock::FaceId;
|
use fontdock::FaceId;
|
||||||
use image::RgbImage;
|
use image::{DynamicImage, GenericImageView};
|
||||||
use pdf_writer::{
|
use pdf_writer::{
|
||||||
CidFontType, ColorSpace, Content, FontFlags, Name, PdfWriter, Rect, Ref, Str,
|
CidFontType, ColorSpace, Content, FontFlags, Name, PdfWriter, Rect, Ref, Str,
|
||||||
SystemInfo, UnicodeCmap,
|
SystemInfo, UnicodeCmap,
|
||||||
};
|
};
|
||||||
use ttf_parser::{name_id, GlyphId};
|
use ttf_parser::{name_id, GlyphId};
|
||||||
|
|
||||||
use crate::font::FontLoader;
|
use crate::env::{Env, ResourceId};
|
||||||
use crate::geom::Length;
|
use crate::geom::Length;
|
||||||
use crate::layout::{BoxLayout, LayoutElement};
|
use crate::layout::{BoxLayout, LayoutElement};
|
||||||
|
|
||||||
@ -21,14 +21,14 @@ use crate::layout::{BoxLayout, LayoutElement};
|
|||||||
/// included in the _PDF_.
|
/// included in the _PDF_.
|
||||||
///
|
///
|
||||||
/// Returns the raw bytes making up the _PDF_ document.
|
/// Returns the raw bytes making up the _PDF_ document.
|
||||||
pub fn export(layouts: &[BoxLayout], loader: &FontLoader) -> Vec<u8> {
|
pub fn export(layouts: &[BoxLayout], env: &Env) -> Vec<u8> {
|
||||||
PdfExporter::new(layouts, loader).write()
|
PdfExporter::new(layouts, env).write()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PdfExporter<'a> {
|
struct PdfExporter<'a> {
|
||||||
writer: PdfWriter,
|
writer: PdfWriter,
|
||||||
layouts: &'a [BoxLayout],
|
layouts: &'a [BoxLayout],
|
||||||
loader: &'a FontLoader,
|
env: &'a Env,
|
||||||
/// We need to know exactly which indirect reference id will be used for
|
/// We need to know exactly which indirect reference id will be used for
|
||||||
/// which objects up-front to correctly declare the document catalogue, page
|
/// which objects up-front to correctly declare the document catalogue, page
|
||||||
/// tree and so on. These offsets are computed in the beginning and stored
|
/// tree and so on. These offsets are computed in the beginning and stored
|
||||||
@ -41,13 +41,13 @@ struct PdfExporter<'a> {
|
|||||||
/// Backwards from the pdf indices to the old face ids.
|
/// Backwards from the pdf indices to the old face ids.
|
||||||
fonts_to_layout: Vec<FaceId>,
|
fonts_to_layout: Vec<FaceId>,
|
||||||
/// The already visited images.
|
/// The already visited images.
|
||||||
images: Vec<&'a RgbImage>,
|
images: Vec<ResourceId>,
|
||||||
/// The total number of images.
|
/// The total number of images.
|
||||||
image_count: usize,
|
image_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PdfExporter<'a> {
|
impl<'a> PdfExporter<'a> {
|
||||||
fn new(layouts: &'a [BoxLayout], loader: &'a FontLoader) -> Self {
|
fn new(layouts: &'a [BoxLayout], env: &'a Env) -> Self {
|
||||||
let mut writer = PdfWriter::new(1, 7);
|
let mut writer = PdfWriter::new(1, 7);
|
||||||
writer.set_indent(2);
|
writer.set_indent(2);
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
Self {
|
Self {
|
||||||
writer,
|
writer,
|
||||||
layouts,
|
layouts,
|
||||||
loader,
|
env,
|
||||||
refs,
|
refs,
|
||||||
fonts_to_pdf,
|
fonts_to_pdf,
|
||||||
fonts_to_layout,
|
fonts_to_layout,
|
||||||
@ -185,7 +185,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
content.x_object(Name(name.as_bytes()));
|
content.x_object(Name(name.as_bytes()));
|
||||||
content.restore_state();
|
content.restore_state();
|
||||||
|
|
||||||
self.images.push(&image.buf);
|
self.images.push(image.resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ impl<'a> PdfExporter<'a> {
|
|||||||
|
|
||||||
fn write_fonts(&mut self) {
|
fn write_fonts(&mut self) {
|
||||||
for (refs, &face_id) in self.refs.fonts().zip(&self.fonts_to_layout) {
|
for (refs, &face_id) in self.refs.fonts().zip(&self.fonts_to_layout) {
|
||||||
let owned_face = self.loader.get_loaded(face_id);
|
let owned_face = self.env.fonts.get_loaded(face_id);
|
||||||
let face = owned_face.get();
|
let face = owned_face.get();
|
||||||
|
|
||||||
let name = face
|
let name = face
|
||||||
@ -302,9 +302,11 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_images(&mut self) {
|
fn write_images(&mut self) {
|
||||||
for (id, image) in self.refs.images().zip(&self.images) {
|
for (id, &resource) in self.refs.images().zip(&self.images) {
|
||||||
|
let image = self.env.resources.get_loaded::<DynamicImage>(resource);
|
||||||
|
let data = image.to_rgb8().into_raw();
|
||||||
self.writer
|
self.writer
|
||||||
.image_stream(id, &image.as_raw())
|
.image_stream(id, &data)
|
||||||
.width(image.width() as i32)
|
.width(image.width() as i32)
|
||||||
.height(image.height() as i32)
|
.height(image.height() as i32)
|
||||||
.color_space(ColorSpace::DeviceRGB)
|
.color_space(ColorSpace::DeviceRGB)
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
//! Font handling.
|
//! Font handling.
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use fontdock::{ContainsChar, FaceFromVec, FontSource};
|
use fontdock::{ContainsChar, FaceFromVec, FontSource};
|
||||||
use ttf_parser::Face;
|
use ttf_parser::Face;
|
||||||
|
|
||||||
/// A reference-counted shared font loader backed by a dynamic font source.
|
|
||||||
pub type SharedFontLoader = Rc<RefCell<FontLoader>>;
|
|
||||||
|
|
||||||
/// A font loader backed by a dynamic source.
|
/// A font loader backed by a dynamic source.
|
||||||
pub type FontLoader = fontdock::FontLoader<Box<DynSource>>;
|
pub type FontLoader = fontdock::FontLoader<Box<DynSource>>;
|
||||||
|
|
||||||
|
@ -8,9 +8,7 @@ mod spacing;
|
|||||||
mod stack;
|
mod stack;
|
||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
use image::RgbImage;
|
use crate::env::{ResourceId, SharedEnv};
|
||||||
|
|
||||||
use crate::font::SharedFontLoader;
|
|
||||||
use crate::geom::*;
|
use crate::geom::*;
|
||||||
use crate::shaping::Shaped;
|
use crate::shaping::Shaped;
|
||||||
|
|
||||||
@ -23,16 +21,16 @@ 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 fn layout(document: &Document, loader: SharedFontLoader) -> Vec<BoxLayout> {
|
pub fn layout(document: &Document, env: SharedEnv) -> Vec<BoxLayout> {
|
||||||
let mut ctx = LayoutContext { loader };
|
let mut ctx = LayoutContext { env };
|
||||||
document.layout(&mut ctx)
|
document.layout(&mut ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The context for layouting.
|
/// The context for layouting.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LayoutContext {
|
pub struct LayoutContext {
|
||||||
/// The font loader to query fonts from when typesetting text.
|
/// The environment from which fonts are gathered.
|
||||||
pub loader: SharedFontLoader,
|
pub env: SharedEnv,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Layout a node.
|
/// Layout a node.
|
||||||
@ -185,7 +183,7 @@ pub enum LayoutElement {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ImageElement {
|
pub struct ImageElement {
|
||||||
/// The image.
|
/// The image.
|
||||||
pub buf: RgbImage,
|
pub resource: ResourceId,
|
||||||
/// The document size of the image.
|
/// The document size of the image.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
}
|
}
|
||||||
|
@ -25,10 +25,10 @@ pub struct Text {
|
|||||||
|
|
||||||
impl Layout for Text {
|
impl Layout for Text {
|
||||||
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
|
fn layout(&self, ctx: &mut LayoutContext, _: &Areas) -> Layouted {
|
||||||
let mut loader = ctx.loader.borrow_mut();
|
let mut env = ctx.env.borrow_mut();
|
||||||
Layouted::Layout(
|
Layouted::Layout(
|
||||||
shaping::shape(
|
shaping::shape(
|
||||||
&mut loader,
|
&mut env.fonts,
|
||||||
&self.text,
|
&self.text,
|
||||||
self.dir,
|
self.dir,
|
||||||
self.font_size,
|
self.font_size,
|
||||||
|
16
src/lib.rs
16
src/lib.rs
@ -29,6 +29,7 @@ pub mod diag;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
|
pub mod env;
|
||||||
pub mod export;
|
pub mod export;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod geom;
|
pub mod geom;
|
||||||
@ -40,19 +41,18 @@ pub mod prelude;
|
|||||||
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::eval::State;
|
use crate::eval::State;
|
||||||
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 fn typeset(
|
pub fn typeset(src: &str, env: SharedEnv, state: State) -> Pass<Vec<BoxLayout>> {
|
||||||
src: &str,
|
|
||||||
state: State,
|
|
||||||
loader: SharedFontLoader,
|
|
||||||
) -> 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 } =
|
||||||
let layouts = layout::layout(&document, loader);
|
eval::eval(&tree, Rc::clone(&env), state);
|
||||||
|
let layouts = layout::layout(&document, env);
|
||||||
Pass::new(layouts, Feedback::join(f1, f2))
|
Pass::new(layouts, Feedback::join(f1, f2))
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::io::Cursor;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::BufReader;
|
|
||||||
|
|
||||||
use image::io::Reader;
|
use image::io::Reader;
|
||||||
use image::RgbImage;
|
use image::GenericImageView;
|
||||||
|
|
||||||
|
use crate::env::ResourceId;
|
||||||
use crate::layout::*;
|
use crate::layout::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@ -20,25 +19,27 @@ pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
let height = args.get::<_, Linear>(ctx, "height");
|
let height = args.get::<_, Linear>(ctx, "height");
|
||||||
|
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
if let Ok(file) = File::open(path.v) {
|
let mut env = ctx.env.borrow_mut();
|
||||||
match Reader::new(BufReader::new(file))
|
let loaded = env.resources.load(path.v, |data| {
|
||||||
|
Reader::new(Cursor::new(data))
|
||||||
.with_guessed_format()
|
.with_guessed_format()
|
||||||
.map_err(|err| err.into())
|
.ok()
|
||||||
.and_then(|reader| reader.decode())
|
.and_then(|reader| reader.decode().ok())
|
||||||
.map(|img| img.into_rgb8())
|
});
|
||||||
{
|
|
||||||
Ok(buf) => {
|
if let Some((resource, buf)) = loaded {
|
||||||
|
let dimensions = buf.dimensions();
|
||||||
|
drop(env);
|
||||||
ctx.push(Image {
|
ctx.push(Image {
|
||||||
buf,
|
resource,
|
||||||
|
dimensions,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
align: ctx.state.align,
|
align: ctx.state.align,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
Err(err) => ctx.diag(error!(path.span, "invalid image: {}", err)),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ctx.diag(error!(path.span, "failed to open image file"));
|
drop(env);
|
||||||
|
ctx.diag(error!(path.span, "failed to load image"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,10 +47,12 @@ pub fn image(mut args: Args, ctx: &mut EvalContext) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An image node.
|
/// An image node.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
struct Image {
|
struct Image {
|
||||||
/// The image.
|
/// The resource id of the image file.
|
||||||
buf: RgbImage,
|
resource: ResourceId,
|
||||||
|
/// The pixel dimensions of the image.
|
||||||
|
dimensions: (u32, u32),
|
||||||
/// The fixed width, if any.
|
/// The fixed width, if any.
|
||||||
width: Option<Linear>,
|
width: Option<Linear>,
|
||||||
/// The fixed height, if any.
|
/// The fixed height, if any.
|
||||||
@ -61,8 +64,7 @@ struct Image {
|
|||||||
impl Layout for Image {
|
impl Layout for Image {
|
||||||
fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
|
fn layout(&self, _: &mut LayoutContext, areas: &Areas) -> Layouted {
|
||||||
let Area { rem, full } = areas.current;
|
let Area { rem, full } = areas.current;
|
||||||
let (pixel_width, pixel_height) = self.buf.dimensions();
|
let pixel_ratio = (self.dimensions.0 as f64) / (self.dimensions.1 as f64);
|
||||||
let pixel_ratio = (pixel_width as f64) / (pixel_height as f64);
|
|
||||||
|
|
||||||
let width = self.width.map(|w| w.resolve(full.width));
|
let width = self.width.map(|w| w.resolve(full.width));
|
||||||
let height = self.height.map(|w| w.resolve(full.height));
|
let height = self.height.map(|w| w.resolve(full.height));
|
||||||
@ -85,23 +87,13 @@ impl Layout for Image {
|
|||||||
let mut boxed = BoxLayout::new(size);
|
let mut boxed = BoxLayout::new(size);
|
||||||
boxed.push(
|
boxed.push(
|
||||||
Point::ZERO,
|
Point::ZERO,
|
||||||
LayoutElement::Image(ImageElement { buf: self.buf.clone(), size }),
|
LayoutElement::Image(ImageElement { resource: self.resource, size }),
|
||||||
);
|
);
|
||||||
|
|
||||||
Layouted::Layout(boxed, self.align)
|
Layouted::Layout(boxed, self.align)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Image {
|
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Image")
|
|
||||||
.field("width", &self.width)
|
|
||||||
.field("height", &self.height)
|
|
||||||
.field("align", &self.align)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Image> for LayoutNode {
|
impl From<Image> for LayoutNode {
|
||||||
fn from(image: Image) -> Self {
|
fn from(image: Image) -> Self {
|
||||||
Self::dynamic(image)
|
Self::dynamic(image)
|
||||||
|
13
src/main.rs
13
src/main.rs
@ -7,6 +7,7 @@ use anyhow::{anyhow, bail, Context};
|
|||||||
use fontdock::fs::{FsIndex, FsSource};
|
use fontdock::fs::{FsIndex, FsSource};
|
||||||
|
|
||||||
use typst::diag::{Feedback, Pass};
|
use typst::diag::{Feedback, Pass};
|
||||||
|
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::FontLoader;
|
||||||
@ -41,16 +42,16 @@ fn main() -> anyhow::Result<()> {
|
|||||||
index.search_os();
|
index.search_os();
|
||||||
|
|
||||||
let (files, descriptors) = index.into_vecs();
|
let (files, descriptors) = index.into_vecs();
|
||||||
let loader = Rc::new(RefCell::new(FontLoader::new(
|
let env = Rc::new(RefCell::new(Env {
|
||||||
Box::new(FsSource::new(files)),
|
fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors),
|
||||||
descriptors,
|
resources: ResourceLoader::new(),
|
||||||
)));
|
}));
|
||||||
|
|
||||||
let state = State::default();
|
let state = State::default();
|
||||||
let Pass {
|
let Pass {
|
||||||
output: layouts,
|
output: layouts,
|
||||||
feedback: Feedback { mut diags, .. },
|
feedback: Feedback { mut diags, .. },
|
||||||
} = typeset(&src, state, Rc::clone(&loader));
|
} = typeset(&src, Rc::clone(&env), state);
|
||||||
|
|
||||||
if !diags.is_empty() {
|
if !diags.is_empty() {
|
||||||
diags.sort();
|
diags.sort();
|
||||||
@ -71,7 +72,7 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pdf_data = pdf::export(&layouts, &loader.borrow());
|
let pdf_data = pdf::export(&layouts, &env.borrow());
|
||||||
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(())
|
||||||
|
@ -6,6 +6,7 @@ use std::path::Path;
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use fontdock::fs::{FsIndex, FsSource};
|
use fontdock::fs::{FsIndex, FsSource};
|
||||||
|
use image::{DynamicImage, GenericImageView, Rgba};
|
||||||
use memmap::Mmap;
|
use memmap::Mmap;
|
||||||
use tiny_skia::{
|
use tiny_skia::{
|
||||||
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
Canvas, Color, ColorU8, FillRule, FilterQuality, Paint, PathBuilder, Pattern, Pixmap,
|
||||||
@ -14,9 +15,10 @@ use tiny_skia::{
|
|||||||
use ttf_parser::OutlineBuilder;
|
use ttf_parser::OutlineBuilder;
|
||||||
|
|
||||||
use typst::diag::{Feedback, Pass};
|
use typst::diag::{Feedback, Pass};
|
||||||
|
use typst::env::{Env, ResourceLoader, SharedEnv};
|
||||||
use typst::eval::State;
|
use typst::eval::State;
|
||||||
use typst::export::pdf;
|
use typst::export::pdf;
|
||||||
use typst::font::{FontLoader, SharedFontLoader};
|
use typst::font::FontLoader;
|
||||||
use typst::geom::{Length, Point};
|
use typst::geom::{Length, Point};
|
||||||
use typst::layout::{BoxLayout, ImageElement, LayoutElement};
|
use typst::layout::{BoxLayout, ImageElement, LayoutElement};
|
||||||
use typst::parse::LineMap;
|
use typst::parse::LineMap;
|
||||||
@ -67,16 +69,16 @@ fn main() {
|
|||||||
index.search_dir(FONT_DIR);
|
index.search_dir(FONT_DIR);
|
||||||
|
|
||||||
let (files, descriptors) = index.into_vecs();
|
let (files, descriptors) = index.into_vecs();
|
||||||
let loader = Rc::new(RefCell::new(FontLoader::new(
|
let env = Rc::new(RefCell::new(Env {
|
||||||
Box::new(FsSource::new(files)),
|
fonts: FontLoader::new(Box::new(FsSource::new(files)), descriptors),
|
||||||
descriptors,
|
resources: ResourceLoader::new(),
|
||||||
)));
|
}));
|
||||||
|
|
||||||
let mut ok = true;
|
let mut ok = true;
|
||||||
|
|
||||||
for (name, src_path, pdf_path, png_path, ref_path) in filtered {
|
for (name, src_path, pdf_path, png_path, ref_path) in filtered {
|
||||||
print!("Testing {}.", name);
|
print!("Testing {}.", name);
|
||||||
test(&src_path, &pdf_path, &png_path, &loader);
|
test(&src_path, &pdf_path, &png_path, &env);
|
||||||
|
|
||||||
let png_file = File::open(&png_path).unwrap();
|
let png_file = File::open(&png_path).unwrap();
|
||||||
let ref_file = match File::open(&ref_path) {
|
let ref_file = match File::open(&ref_path) {
|
||||||
@ -104,13 +106,13 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test(src_path: &Path, pdf_path: &Path, png_path: &Path, loader: &SharedFontLoader) {
|
fn test(src_path: &Path, pdf_path: &Path, png_path: &Path, env: &SharedEnv) {
|
||||||
let src = fs::read_to_string(src_path).unwrap();
|
let src = fs::read_to_string(src_path).unwrap();
|
||||||
let state = State::default();
|
let state = State::default();
|
||||||
let Pass {
|
let Pass {
|
||||||
output: layouts,
|
output: layouts,
|
||||||
feedback: Feedback { mut diags, .. },
|
feedback: Feedback { mut diags, .. },
|
||||||
} = typeset(&src, state, Rc::clone(loader));
|
} = typeset(&src, Rc::clone(env), state);
|
||||||
|
|
||||||
if !diags.is_empty() {
|
if !diags.is_empty() {
|
||||||
diags.sort();
|
diags.sort();
|
||||||
@ -131,12 +133,11 @@ fn test(src_path: &Path, pdf_path: &Path, png_path: &Path, loader: &SharedFontLo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let loader = loader.borrow();
|
let env = env.borrow();
|
||||||
|
let canvas = draw(&layouts, &env, 2.0);
|
||||||
let canvas = draw(&layouts, &loader, 2.0);
|
|
||||||
canvas.pixmap.save_png(png_path).unwrap();
|
canvas.pixmap.save_png(png_path).unwrap();
|
||||||
|
|
||||||
let pdf_data = pdf::export(&layouts, &loader);
|
let pdf_data = pdf::export(&layouts, &env);
|
||||||
fs::write(pdf_path, pdf_data).unwrap();
|
fs::write(pdf_path, pdf_data).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +171,7 @@ impl TestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(layouts: &[BoxLayout], loader: &FontLoader, pixel_per_pt: f32) -> Canvas {
|
fn draw(layouts: &[BoxLayout], env: &Env, pixel_per_pt: f32) -> Canvas {
|
||||||
let pad = Length::pt(5.0);
|
let pad = Length::pt(5.0);
|
||||||
|
|
||||||
let height = pad + layouts.iter().map(|l| l.size.height + pad).sum::<Length>();
|
let height = pad + layouts.iter().map(|l| l.size.height + pad).sum::<Length>();
|
||||||
@ -207,9 +208,11 @@ fn draw(layouts: &[BoxLayout], loader: &FontLoader, pixel_per_pt: f32) -> Canvas
|
|||||||
let pos = origin + pos;
|
let pos = origin + pos;
|
||||||
match element {
|
match element {
|
||||||
LayoutElement::Text(shaped) => {
|
LayoutElement::Text(shaped) => {
|
||||||
draw_text(&mut canvas, loader, shaped, pos)
|
draw_text(&mut canvas, pos, env, shaped);
|
||||||
|
}
|
||||||
|
LayoutElement::Image(image) => {
|
||||||
|
draw_image(&mut canvas, pos, env, image);
|
||||||
}
|
}
|
||||||
LayoutElement::Image(image) => draw_image(&mut canvas, image, pos),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,8 +222,8 @@ fn draw(layouts: &[BoxLayout], loader: &FontLoader, pixel_per_pt: f32) -> Canvas
|
|||||||
canvas
|
canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_text(canvas: &mut Canvas, loader: &FontLoader, shaped: &Shaped, pos: Point) {
|
fn draw_text(canvas: &mut Canvas, pos: Point, env: &Env, shaped: &Shaped) {
|
||||||
let face = loader.get_loaded(shaped.face).get();
|
let face = env.fonts.get_loaded(shaped.face).get();
|
||||||
|
|
||||||
for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) {
|
for (&glyph, &offset) in shaped.glyphs.iter().zip(&shaped.offsets) {
|
||||||
let units_per_em = face.units_per_em().unwrap_or(1000);
|
let units_per_em = face.units_per_em().unwrap_or(1000);
|
||||||
@ -244,11 +247,13 @@ fn draw_text(canvas: &mut Canvas, loader: &FontLoader, shaped: &Shaped, pos: Poi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_image(canvas: &mut Canvas, image: &ImageElement, pos: Point) {
|
fn draw_image(canvas: &mut Canvas, pos: Point, env: &Env, image: &ImageElement) {
|
||||||
let mut pixmap = Pixmap::new(image.buf.width(), image.buf.height()).unwrap();
|
let buf = env.resources.get_loaded::<DynamicImage>(image.resource);
|
||||||
for (src, dest) in image.buf.pixels().zip(pixmap.pixels_mut()) {
|
|
||||||
let [r, g, b] = src.0;
|
let mut pixmap = Pixmap::new(buf.width(), buf.height()).unwrap();
|
||||||
*dest = ColorU8::from_rgba(r, g, b, 255).premultiply();
|
for ((_, _, src), dest) in buf.pixels().zip(pixmap.pixels_mut()) {
|
||||||
|
let Rgba([r, g, b, a]) = src;
|
||||||
|
*dest = ColorU8::from_rgba(r, g, b, a).premultiply();
|
||||||
}
|
}
|
||||||
|
|
||||||
let view_width = image.size.width.to_pt() as f32;
|
let view_width = image.size.width.to_pt() as f32;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user