Remove resource abstraction and handle images natively

This commit is contained in:
Laurenz 2021-05-14 11:14:28 +02:00
parent 33733fd1ef
commit e65c2b949c
9 changed files with 184 additions and 166 deletions

6
src/env/fs.rs vendored
View File

@ -12,7 +12,7 @@ use walkdir::WalkDir;
use super::{Buffer, Loader}; use super::{Buffer, Loader};
use crate::font::{FaceInfo, FontStretch, FontStyle, FontVariant, FontWeight}; use crate::font::{FaceInfo, FontStretch, FontStyle, FontVariant, FontWeight};
/// Loads fonts and resources from the local file system. /// Loads fonts and images from the local file system.
/// ///
/// _This is only available when the `fs` feature is enabled._ /// _This is only available when the `fs` feature is enabled._
#[derive(Default, Debug, Clone, Serialize, Deserialize)] #[derive(Default, Debug, Clone, Serialize, Deserialize)]
@ -175,8 +175,8 @@ impl Loader for FsLoader {
load(&mut self.cache, &self.files[idx]) load(&mut self.cache, &self.files[idx])
} }
fn load_file(&mut self, url: &str) -> Option<Buffer> { fn load_file(&mut self, path: &str) -> Option<Buffer> {
load(&mut self.cache, Path::new(url)) load(&mut self.cache, Path::new(path))
} }
} }

33
src/env/image.rs vendored
View File

@ -4,37 +4,44 @@ use std::io::Cursor;
use image::io::Reader as ImageReader; use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImageView, ImageFormat}; use image::{DynamicImage, GenericImageView, ImageFormat};
use super::Buffer; /// A loaded image.
pub struct Image {
/// A loaded image resource.
pub struct ImageResource {
/// The original format the image was encoded in. /// The original format the image was encoded in.
pub format: ImageFormat, pub format: ImageFormat,
/// The decoded image. /// The decoded image.
pub buf: DynamicImage, pub buf: DynamicImage,
} }
impl ImageResource { impl Image {
/// Parse an image resource from raw data in a supported format. /// Parse an image from raw data in a supported format.
/// ///
/// The image format is determined automatically. /// The image format is determined automatically.
pub fn parse(data: Buffer) -> Option<Self> { pub fn parse(data: &[u8]) -> Option<Self> {
let cursor = Cursor::new(data.as_ref()); let cursor = Cursor::new(data);
let reader = ImageReader::new(cursor).with_guessed_format().ok()?; let reader = ImageReader::new(cursor).with_guessed_format().ok()?;
let format = reader.format()?; let format = reader.format()?;
let buf = reader.decode().ok()?; let buf = reader.decode().ok()?;
Some(Self { format, buf }) Some(Self { format, buf })
} }
/// The width of the image.
pub fn width(&self) -> u32 {
self.buf.width()
} }
impl Debug for ImageResource { /// The height of the image.
pub fn height(&self) -> u32 {
self.buf.height()
}
}
impl Debug for Image {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let (width, height) = self.buf.dimensions(); f.debug_struct("Image")
f.debug_struct("ImageResource")
.field("format", &self.format) .field("format", &self.format)
.field("color", &self.buf.color()) .field("color", &self.buf.color())
.field("width", &width) .field("width", &self.width())
.field("height", &height) .field("height", &self.height())
.finish() .finish()
} }
} }

122
src/env/mod.rs vendored
View File

@ -1,4 +1,4 @@
//! Font and resource loading. //! Font and image loading.
#[cfg(feature = "fs")] #[cfg(feature = "fs")]
mod fs; mod fs;
@ -8,7 +8,6 @@ pub use self::image::*;
#[cfg(feature = "fs")] #[cfg(feature = "fs")]
pub use fs::*; pub use fs::*;
use std::any::Any;
use std::collections::{hash_map::Entry, HashMap}; use std::collections::{hash_map::Entry, HashMap};
use std::rc::Rc; use std::rc::Rc;
@ -16,18 +15,22 @@ use serde::{Deserialize, Serialize};
use crate::font::{Face, FaceInfo, FontVariant}; use crate::font::{Face, FaceInfo, FontVariant};
/// Handles font and resource loading. /// Handles font and image loading.
pub struct Env { pub struct Env {
/// The loader that serves the font face and file buffers. /// The loader that serves the font face and file buffers.
loader: Box<dyn Loader>, loader: Box<dyn Loader>,
/// Loaded resources indexed by [`ResourceId`].
resources: Vec<Box<dyn Any>>,
/// Maps from URL to loaded resource.
urls: HashMap<String, ResourceId>,
/// Faces indexed by [`FaceId`]. `None` if not yet loaded. /// Faces indexed by [`FaceId`]. `None` if not yet loaded.
faces: Vec<Option<Face>>, faces: Vec<Option<Face>>,
/// Maps a family name to the ids of all faces that are part of the family. /// Maps a family name to the ids of all faces that are part of the family.
families: HashMap<String, Vec<FaceId>>, families: HashMap<String, Vec<FaceId>>,
/// Loaded images indexed by [`ImageId`].
images: Vec<Image>,
/// Maps from paths to loaded images.
paths: HashMap<String, ImageId>,
/// Callback for loaded font faces.
on_face_load: Option<Box<dyn Fn(FaceId, &Face)>>,
/// Callback for loaded images.
on_image_load: Option<Box<dyn Fn(ImageId, &Image)>>,
} }
impl Env { impl Env {
@ -49,10 +52,12 @@ impl Env {
Self { Self {
loader: Box::new(loader), loader: Box::new(loader),
resources: vec![],
urls: HashMap::new(),
faces, faces,
families, families,
images: vec![],
paths: HashMap::new(),
on_face_load: None,
on_image_load: None,
} }
} }
@ -113,41 +118,24 @@ impl Env {
} }
// Load the face if it's not already loaded. // Load the face if it's not already loaded.
let idx = best?.0 as usize; let id = best?;
let idx = id.0 as usize;
let slot = &mut self.faces[idx]; let slot = &mut self.faces[idx];
if slot.is_none() { if slot.is_none() {
let index = infos[idx].index; let index = infos[idx].index;
let buffer = self.loader.load_face(idx)?; let buffer = self.loader.load_face(idx)?;
let face = Face::new(buffer, index)?; let face = Face::new(buffer, index)?;
if let Some(callback) = &self.on_face_load {
callback(id, &face);
}
*slot = Some(face); *slot = Some(face);
} }
best best
} }
/// Load a file from a local or remote URL, parse it into a cached resource
/// and return a unique identifier that allows to retrieve the parsed
/// resource through [`resource()`](Self::resource).
pub fn load_resource<F, R>(&mut self, url: &str, parse: F) -> Option<ResourceId>
where
F: FnOnce(Buffer) -> Option<R>,
R: 'static,
{
Some(match self.urls.entry(url.to_string()) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let buffer = self.loader.load_file(url)?;
let resource = parse(buffer)?;
let len = self.resources.len();
self.resources.push(Box::new(resource));
*entry.insert(ResourceId(len as u32))
}
})
}
/// Get a reference to a queried face. /// Get a reference to a queried face.
/// ///
/// # Panics
/// This panics if no face with this id was loaded. This function should /// This panics if no face with this id was loaded. This function should
/// only be called with ids returned by [`query_face()`](Self::query_face). /// only be called with ids returned by [`query_face()`](Self::query_face).
#[track_caller] #[track_caller]
@ -155,20 +143,50 @@ impl Env {
self.faces[id.0 as usize].as_ref().expect("font face was not loaded") self.faces[id.0 as usize].as_ref().expect("font face was not loaded")
} }
/// Get a reference to a loaded resource. /// Register a callback which is invoked when a font face was loaded.
pub fn on_face_load<F>(&mut self, f: F)
where
F: Fn(FaceId, &Face) + 'static,
{
self.on_face_load = Some(Box::new(f));
}
/// Load and decode an image file from a path.
pub fn load_image(&mut self, path: &str) -> Option<ImageId> {
Some(match self.paths.entry(path.to_string()) {
Entry::Occupied(entry) => *entry.get(),
Entry::Vacant(entry) => {
let buffer = self.loader.load_file(path)?;
let image = Image::parse(&buffer)?;
let id = ImageId(self.images.len() as u32);
if let Some(callback) = &self.on_image_load {
callback(id, &image);
}
self.images.push(image);
*entry.insert(id)
}
})
}
/// Get a reference to a loaded image.
/// ///
/// This panics if no resource with this id was loaded. This function should /// This panics if no image with this id was loaded. This function should
/// only be called with ids returned by /// only be called with ids returned by [`load_image()`](Self::load_image).
/// [`load_resource()`](Self::load_resource).
#[track_caller] #[track_caller]
pub fn resource<R: 'static>(&self, id: ResourceId) -> &R { pub fn image(&self, id: ImageId) -> &Image {
self.resources[id.0 as usize] &self.images[id.0 as usize]
.downcast_ref() }
.expect("bad resource type")
/// Register a callback which is invoked when an image was loaded.
pub fn on_image_load<F>(&mut self, f: F)
where
F: Fn(ImageId, &Image) + 'static,
{
self.on_image_load = Some(Box::new(f));
} }
} }
/// Loads fonts and resources from a remote or local source. /// Loads fonts and images from a remote or local source.
pub trait Loader { pub trait Loader {
/// Descriptions of all font faces this loader serves. /// Descriptions of all font faces this loader serves.
fn faces(&self) -> &[FaceInfo]; fn faces(&self) -> &[FaceInfo];
@ -176,8 +194,8 @@ pub trait Loader {
/// Load the font face with the given index in [`faces()`](Self::faces). /// Load the font face with the given index in [`faces()`](Self::faces).
fn load_face(&mut self, idx: usize) -> Option<Buffer>; fn load_face(&mut self, idx: usize) -> Option<Buffer>;
/// Load a file from a URL. /// Load a file from a path.
fn load_file(&mut self, url: &str) -> Option<Buffer>; fn load_file(&mut self, path: &str) -> Option<Buffer>;
} }
/// A shared byte buffer. /// A shared byte buffer.
@ -205,11 +223,21 @@ impl FaceId {
} }
} }
/// A unique identifier for a loaded resource. /// A unique identifier for a loaded image.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ResourceId(u32); pub struct ImageId(u32);
impl ResourceId { impl ImageId {
/// A blank initialization value. /// Create an image id from the raw underlying value.
pub const MAX: Self = Self(u32::MAX); ///
/// This should only be called with values returned by
/// [`into_raw`](Self::into_raw).
pub fn from_raw(v: u32) -> Self {
Self(v)
}
/// Convert into the raw underlying value.
pub fn into_raw(self) -> u32 {
self.0
}
} }

View File

@ -30,7 +30,7 @@ impl Layout for BackgroundNode {
} }
}; };
let element = Element::Geometry(Geometry { shape, fill: self.fill }); let element = Element::Geometry(shape, self.fill);
frame.elements.insert(0, (point, element)); frame.elements.insert(0, (point, element));
} }

View File

@ -1,5 +1,5 @@
use crate::color::Color; use crate::color::Color;
use crate::env::{FaceId, ResourceId}; use crate::env::{FaceId, ImageId};
use crate::geom::{Length, Path, Point, Size}; use crate::geom::{Length, Path, Point, Size};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -41,9 +41,9 @@ pub enum Element {
/// Shaped text. /// Shaped text.
Text(Text), Text(Text),
/// A geometric shape. /// A geometric shape.
Geometry(Geometry), Geometry(Shape, Fill),
/// A raster image. /// A raster image.
Image(Image), Image(ImageId, Size),
} }
/// A run of shaped text. /// A run of shaped text.
@ -54,7 +54,7 @@ pub struct Text {
/// The font size. /// The font size.
pub size: Length, pub size: Length,
/// The glyph fill color / texture. /// The glyph fill color / texture.
pub color: Fill, pub fill: Fill,
/// The glyphs. /// The glyphs.
pub glyphs: Vec<Glyph>, pub glyphs: Vec<Glyph>,
} }
@ -83,19 +83,6 @@ impl Text {
} }
} }
/// A shape with some kind of fill.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Geometry {
/// The shape to draw.
pub shape: Shape,
/// How the shape looks on the inside.
//
// TODO: This could be made into a Vec<Fill> or something such that
// the user can compose multiple fills with alpha values less
// than one to achieve cool effects.
pub fill: Fill,
}
/// Some shape. /// Some shape.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Shape { pub enum Shape {
@ -113,12 +100,3 @@ pub enum Fill {
/// The fill is a color. /// The fill is a color.
Color(Color), Color(Color),
} }
/// An image element.
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub struct Image {
/// The image resource.
pub id: ResourceId,
/// The size of the image in the document.
pub size: Size,
}

View File

@ -70,7 +70,7 @@ impl<'a> ShapedText<'a> {
let mut text = Text { let mut text = Text {
face_id, face_id,
size: self.props.size, size: self.props.size,
color: self.props.color, fill: self.props.color,
glyphs: vec![], glyphs: vec![],
}; };

View File

@ -1,8 +1,8 @@
use ::image::GenericImageView; use ::image::GenericImageView;
use super::*; use super::*;
use crate::env::{ImageResource, ResourceId}; use crate::env::ImageId;
use crate::layout::{AnyNode, Areas, Element, Frame, Image, Layout, LayoutContext}; use crate::layout::{AnyNode, Areas, Element, Frame, Layout, LayoutContext};
/// `image`: An image. /// `image`: An image.
/// ///
@ -20,9 +20,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
Value::template("image", move |ctx| { Value::template("image", move |ctx| {
if let Some(path) = &path { if let Some(path) = &path {
let loaded = ctx.env.load_resource(&path.v, ImageResource::parse); if let Some(id) = ctx.env.load_image(&path.v) {
if let Some(id) = loaded { let img = ctx.env.image(id);
let img = ctx.env.resource::<ImageResource>(id);
let dimensions = img.buf.dimensions(); let dimensions = img.buf.dimensions();
ctx.push(ImageNode { id, dimensions, width, height }); ctx.push(ImageNode { id, dimensions, width, height });
} else { } else {
@ -35,8 +34,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
/// An image node. /// An image node.
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
struct ImageNode { struct ImageNode {
/// The resource id of the image file. /// The id of the image file.
id: ResourceId, id: ImageId,
/// The pixel dimensions of the image. /// The pixel dimensions of the image.
dimensions: (u32, u32), dimensions: (u32, u32),
/// The fixed width, if any. /// The fixed width, if any.
@ -75,7 +74,7 @@ impl Layout for ImageNode {
}; };
let mut frame = Frame::new(size, size.height); let mut frame = Frame::new(size, size.height);
frame.push(Point::ZERO, Element::Image(Image { id: self.id, size })); frame.push(Point::ZERO, Element::Image(self.id, size));
vec![frame] vec![frame]
} }

View File

@ -13,10 +13,10 @@ use pdf_writer::{
use ttf_parser::{name_id, GlyphId}; use ttf_parser::{name_id, GlyphId};
use crate::color::Color; use crate::color::Color;
use crate::env::{Env, FaceId, ImageResource, ResourceId}; use crate::env::{Env, FaceId, Image, ImageId};
use crate::font::{Em, VerticalFontMetric}; use crate::font::{Em, VerticalFontMetric};
use crate::geom::{self, Length, Size}; use crate::geom::{self, Length, Size};
use crate::layout::{Element, Fill, Frame, Image, Shape}; use crate::layout::{Element, Fill, Frame, Shape};
/// Export a collection of frames into a PDF document. /// Export a collection of frames into a PDF document.
/// ///
@ -35,7 +35,7 @@ struct PdfExporter<'a> {
env: &'a Env, env: &'a Env,
refs: Refs, refs: Refs,
fonts: Remapper<FaceId>, fonts: Remapper<FaceId>,
images: Remapper<ResourceId>, images: Remapper<ImageId>,
} }
impl<'a> PdfExporter<'a> { impl<'a> PdfExporter<'a> {
@ -49,16 +49,16 @@ impl<'a> PdfExporter<'a> {
for frame in frames { for frame in frames {
for (_, element) in &frame.elements { for (_, element) in &frame.elements {
match element { match *element {
Element::Text(shaped) => fonts.insert(shaped.face_id), Element::Text(ref shaped) => fonts.insert(shaped.face_id),
Element::Image(image) => { Element::Geometry(_, _) => {}
let img = env.resource::<ImageResource>(image.id); Element::Image(id, _) => {
let img = env.image(id);
if img.buf.color().has_alpha() { if img.buf.color().has_alpha() {
alpha_masks += 1; alpha_masks += 1;
} }
images.insert(image.id); images.insert(id);
} }
Element::Geometry(_) => {}
} }
} }
} }
@ -139,23 +139,35 @@ impl<'a> PdfExporter<'a> {
let x = pos.x.to_pt() as f32; let x = pos.x.to_pt() as f32;
let y = (page.size.height - pos.y).to_pt() as f32; let y = (page.size.height - pos.y).to_pt() as f32;
match element { match *element {
&Element::Image(Image { id, size: Size { width, height } }) => { Element::Text(ref shaped) => {
let name = format!("Im{}", self.images.map(id)); if fill != Some(shaped.fill) {
let w = width.to_pt() as f32; write_fill(&mut content, shaped.fill);
let h = height.to_pt() as f32; fill = Some(shaped.fill);
content.save_state();
content.matrix(w, 0.0, 0.0, h, x, y - h);
content.x_object(Name(name.as_bytes()));
content.restore_state();
} }
Element::Geometry(geometry) => { let mut text = content.text();
content.save_state();
write_fill(&mut content, geometry.fill);
match geometry.shape { // Then, also check if we need to issue a font switching
// action.
if shaped.face_id != face || shaped.size != size {
face = shaped.face_id;
size = shaped.size;
let name = format!("F{}", self.fonts.map(shaped.face_id));
text.font(Name(name.as_bytes()), size.to_pt() as f32);
}
// TODO: Respect individual glyph offsets.
text.matrix(1.0, 0.0, 0.0, 1.0, x, y);
text.show(&shaped.encode_glyphs_be());
}
Element::Geometry(ref shape, fill) => {
content.save_state();
write_fill(&mut content, fill);
match *shape {
Shape::Rect(Size { width, height }) => { Shape::Rect(Size { width, height }) => {
let w = width.to_pt() as f32; let w = width.to_pt() as f32;
let h = height.to_pt() as f32; let h = height.to_pt() as f32;
@ -177,27 +189,15 @@ impl<'a> PdfExporter<'a> {
content.restore_state(); content.restore_state();
} }
Element::Text(shaped) => { Element::Image(id, Size { width, height }) => {
if fill != Some(shaped.color) { let name = format!("Im{}", self.images.map(id));
write_fill(&mut content, shaped.color); let w = width.to_pt() as f32;
fill = Some(shaped.color); let h = height.to_pt() as f32;
}
let mut text = content.text(); content.save_state();
content.matrix(w, 0.0, 0.0, h, x, y - h);
// Then, also check if we need to issue a font switching content.x_object(Name(name.as_bytes()));
// action. content.restore_state();
if shaped.face_id != face || shaped.size != size {
face = shaped.face_id;
size = shaped.size;
let name = format!("F{}", self.fonts.map(shaped.face_id));
text.font(Name(name.as_bytes()), size.to_pt() as f32);
}
// TODO: Respect individual glyph offsets.
text.matrix(1.0, 0.0, 0.0, 1.0, x, y);
text.show(&shaped.encode_glyphs_be());
} }
} }
} }
@ -311,8 +311,8 @@ impl<'a> PdfExporter<'a> {
fn write_images(&mut self) { fn write_images(&mut self) {
let mut masks_seen = 0; let mut masks_seen = 0;
for (id, resource) in self.refs.images().zip(self.images.layout_indices()) { for (id, image_id) in self.refs.images().zip(self.images.layout_indices()) {
let img = self.env.resource::<ImageResource>(resource); let img = self.env.image(image_id);
let (width, height) = img.buf.dimensions(); let (width, height) = img.buf.dimensions();
// Add the primary image. // Add the primary image.
@ -397,7 +397,7 @@ const DEFLATE_LEVEL: u8 = 6;
/// Encode an image with a suitable filter. /// Encode an image with a suitable filter.
/// ///
/// Skips the alpha channel as that's encoded separately. /// Skips the alpha channel as that's encoded separately.
fn encode_image(img: &ImageResource) -> ImageResult<(Vec<u8>, Filter, ColorSpace)> { fn encode_image(img: &Image) -> ImageResult<(Vec<u8>, Filter, ColorSpace)> {
let mut data = vec![]; let mut data = vec![];
let (filter, space) = match (img.format, &img.buf) { let (filter, space) = match (img.format, &img.buf) {
// 8-bit gray JPEG. // 8-bit gray JPEG.
@ -438,7 +438,7 @@ fn encode_image(img: &ImageResource) -> ImageResult<(Vec<u8>, Filter, ColorSpace
} }
/// Encode an image's alpha channel if present. /// Encode an image's alpha channel if present.
fn encode_alpha(img: &ImageResource) -> (Vec<u8>, Filter) { fn encode_alpha(img: &Image) -> (Vec<u8>, Filter) {
let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect(); let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
let data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL); let data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL);
(data, Filter::FlateDecode) (data, Filter::FlateDecode)

View File

@ -15,11 +15,11 @@ use walkdir::WalkDir;
use typst::color; use typst::color;
use typst::diag::{Diag, DiagSet, Level, Pass}; use typst::diag::{Diag, DiagSet, Level, Pass};
use typst::env::{Env, FsLoader, ImageResource}; use typst::env::{Env, FsLoader, ImageId};
use typst::eval::{EvalContext, FuncArgs, FuncValue, Scope, Value}; use typst::eval::{EvalContext, FuncArgs, FuncValue, Scope, Value};
use typst::exec::State; use typst::exec::State;
use typst::geom::{self, Length, Point, Sides, Size}; use typst::geom::{self, Length, Point, Sides, Size};
use typst::layout::{Element, Fill, Frame, Geometry, Image, Shape, Text}; use typst::layout::{Element, Fill, Frame, Shape, Text};
use typst::library; use typst::library;
use typst::parse::{LineMap, Scanner}; use typst::parse::{LineMap, Scanner};
use typst::pdf; use typst::pdf;
@ -385,15 +385,21 @@ fn draw(env: &Env, frames: &[Frame], dpi: f32) -> Pixmap {
None, None,
); );
for &(pos, ref element) in &frame.elements { for (pos, element) in &frame.elements {
let pos = origin + pos; let global = origin + *pos;
let x = pos.x.to_pt() as f32; let x = global.x.to_pt() as f32;
let y = pos.y.to_pt() as f32; let y = global.y.to_pt() as f32;
let ts = ts.pre_translate(x, y); let ts = ts.pre_translate(x, y);
match element { match *element {
Element::Text(shaped) => draw_text(&mut canvas, env, ts, shaped), Element::Text(ref text) => {
Element::Image(image) => draw_image(&mut canvas, env, ts, image), draw_text(&mut canvas, env, ts, text);
Element::Geometry(geom) => draw_geometry(&mut canvas, ts, geom), }
Element::Geometry(ref shape, fill) => {
draw_geometry(&mut canvas, ts, shape, fill);
}
Element::Image(id, size) => {
draw_image(&mut canvas, env, ts, id, size);
}
} }
} }
@ -403,13 +409,13 @@ fn draw(env: &Env, frames: &[Frame], dpi: f32) -> Pixmap {
canvas canvas
} }
fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, shaped: &Text) { fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, text: &Text) {
let ttf = env.face(shaped.face_id).ttf(); let ttf = env.face(text.face_id).ttf();
let mut x = 0.0; let mut x = 0.0;
for glyph in &shaped.glyphs { for glyph in &text.glyphs {
let units_per_em = ttf.units_per_em(); let units_per_em = ttf.units_per_em();
let s = shaped.size.to_pt() as f32 / units_per_em as f32; let s = text.size.to_pt() as f32 / units_per_em as f32;
let dx = glyph.x_offset.to_pt() as f32; let dx = glyph.x_offset.to_pt() as f32;
let ts = ts.pre_translate(x + dx, 0.0); let ts = ts.pre_translate(x + dx, 0.0);
@ -441,7 +447,7 @@ fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, shaped: &Text) {
if ttf.outline_glyph(GlyphId(glyph.id), &mut builder).is_some() { if ttf.outline_glyph(GlyphId(glyph.id), &mut builder).is_some() {
let path = builder.0.finish().unwrap(); let path = builder.0.finish().unwrap();
let ts = ts.pre_scale(s, -s); let ts = ts.pre_scale(s, -s);
let mut paint = convert_typst_fill(shaped.color); let mut paint = convert_typst_fill(text.fill);
paint.anti_alias = true; paint.anti_alias = true;
canvas.fill_path(&path, &paint, FillRule::default(), ts, None); canvas.fill_path(&path, &paint, FillRule::default(), ts, None);
} }
@ -451,11 +457,11 @@ fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, shaped: &Text) {
} }
} }
fn draw_geometry(canvas: &mut Pixmap, ts: Transform, element: &Geometry) { fn draw_geometry(canvas: &mut Pixmap, ts: Transform, shape: &Shape, fill: Fill) {
let paint = convert_typst_fill(element.fill); let paint = convert_typst_fill(fill);
let rule = FillRule::default(); let rule = FillRule::default();
match element.shape { match *shape {
Shape::Rect(Size { width, height }) => { Shape::Rect(Size { width, height }) => {
let w = width.to_pt() as f32; let w = width.to_pt() as f32;
let h = height.to_pt() as f32; let h = height.to_pt() as f32;
@ -473,8 +479,8 @@ fn draw_geometry(canvas: &mut Pixmap, ts: Transform, element: &Geometry) {
}; };
} }
fn draw_image(canvas: &mut Pixmap, env: &Env, ts: Transform, element: &Image) { fn draw_image(canvas: &mut Pixmap, env: &Env, ts: Transform, id: ImageId, size: Size) {
let img = &env.resource::<ImageResource>(element.id); let img = env.image(id);
let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap(); let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) { for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) {
@ -482,8 +488,8 @@ fn draw_image(canvas: &mut Pixmap, env: &Env, ts: Transform, element: &Image) {
*dest = ColorU8::from_rgba(r, g, b, a).premultiply(); *dest = ColorU8::from_rgba(r, g, b, a).premultiply();
} }
let view_width = element.size.width.to_pt() as f32; let view_width = size.width.to_pt() as f32;
let view_height = element.size.height.to_pt() as f32; let view_height = size.height.to_pt() as f32;
let scale_x = view_width as f32 / pixmap.width() as f32; let scale_x = view_width as f32 / pixmap.width() as f32;
let scale_y = view_height as f32 / pixmap.height() as f32; let scale_y = view_height as f32 / pixmap.height() as f32;