mirror of
https://github.com/typst/typst
synced 2025-05-21 04:25:28 +08:00
Remove resource abstraction and handle images natively
This commit is contained in:
parent
33733fd1ef
commit
e65c2b949c
6
src/env/fs.rs
vendored
6
src/env/fs.rs
vendored
@ -12,7 +12,7 @@ use walkdir::WalkDir;
|
||||
use super::{Buffer, Loader};
|
||||
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._
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
@ -175,8 +175,8 @@ impl Loader for FsLoader {
|
||||
load(&mut self.cache, &self.files[idx])
|
||||
}
|
||||
|
||||
fn load_file(&mut self, url: &str) -> Option<Buffer> {
|
||||
load(&mut self.cache, Path::new(url))
|
||||
fn load_file(&mut self, path: &str) -> Option<Buffer> {
|
||||
load(&mut self.cache, Path::new(path))
|
||||
}
|
||||
}
|
||||
|
||||
|
33
src/env/image.rs
vendored
33
src/env/image.rs
vendored
@ -4,37 +4,44 @@ use std::io::Cursor;
|
||||
use image::io::Reader as ImageReader;
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat};
|
||||
|
||||
use super::Buffer;
|
||||
|
||||
/// A loaded image resource.
|
||||
pub struct ImageResource {
|
||||
/// A loaded image.
|
||||
pub struct Image {
|
||||
/// The original format the image was encoded in.
|
||||
pub format: ImageFormat,
|
||||
/// The decoded image.
|
||||
pub buf: DynamicImage,
|
||||
}
|
||||
|
||||
impl ImageResource {
|
||||
/// Parse an image resource from raw data in a supported format.
|
||||
impl Image {
|
||||
/// Parse an image from raw data in a supported format.
|
||||
///
|
||||
/// The image format is determined automatically.
|
||||
pub fn parse(data: Buffer) -> Option<Self> {
|
||||
let cursor = Cursor::new(data.as_ref());
|
||||
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||
let cursor = Cursor::new(data);
|
||||
let reader = ImageReader::new(cursor).with_guessed_format().ok()?;
|
||||
let format = reader.format()?;
|
||||
let buf = reader.decode().ok()?;
|
||||
Some(Self { format, buf })
|
||||
}
|
||||
|
||||
/// The width of the image.
|
||||
pub fn width(&self) -> u32 {
|
||||
self.buf.width()
|
||||
}
|
||||
|
||||
/// The height of the image.
|
||||
pub fn height(&self) -> u32 {
|
||||
self.buf.height()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ImageResource {
|
||||
impl Debug for Image {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
let (width, height) = self.buf.dimensions();
|
||||
f.debug_struct("ImageResource")
|
||||
f.debug_struct("Image")
|
||||
.field("format", &self.format)
|
||||
.field("color", &self.buf.color())
|
||||
.field("width", &width)
|
||||
.field("height", &height)
|
||||
.field("width", &self.width())
|
||||
.field("height", &self.height())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
122
src/env/mod.rs
vendored
122
src/env/mod.rs
vendored
@ -1,4 +1,4 @@
|
||||
//! Font and resource loading.
|
||||
//! Font and image loading.
|
||||
|
||||
#[cfg(feature = "fs")]
|
||||
mod fs;
|
||||
@ -8,7 +8,6 @@ pub use self::image::*;
|
||||
#[cfg(feature = "fs")]
|
||||
pub use fs::*;
|
||||
|
||||
use std::any::Any;
|
||||
use std::collections::{hash_map::Entry, HashMap};
|
||||
use std::rc::Rc;
|
||||
|
||||
@ -16,18 +15,22 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::font::{Face, FaceInfo, FontVariant};
|
||||
|
||||
/// Handles font and resource loading.
|
||||
/// Handles font and image loading.
|
||||
pub struct Env {
|
||||
/// The loader that serves the font face and file buffers.
|
||||
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: Vec<Option<Face>>,
|
||||
/// Maps a family name to the ids of all faces that are part of the family.
|
||||
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 {
|
||||
@ -49,10 +52,12 @@ impl Env {
|
||||
|
||||
Self {
|
||||
loader: Box::new(loader),
|
||||
resources: vec![],
|
||||
urls: HashMap::new(),
|
||||
faces,
|
||||
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.
|
||||
let idx = best?.0 as usize;
|
||||
let id = best?;
|
||||
let idx = id.0 as usize;
|
||||
let slot = &mut self.faces[idx];
|
||||
if slot.is_none() {
|
||||
let index = infos[idx].index;
|
||||
let buffer = self.loader.load_face(idx)?;
|
||||
let face = Face::new(buffer, index)?;
|
||||
if let Some(callback) = &self.on_face_load {
|
||||
callback(id, &face);
|
||||
}
|
||||
*slot = Some(face);
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// # Panics
|
||||
/// 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).
|
||||
#[track_caller]
|
||||
@ -155,20 +143,50 @@ impl Env {
|
||||
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
|
||||
/// only be called with ids returned by
|
||||
/// [`load_resource()`](Self::load_resource).
|
||||
/// This panics if no image with this id was loaded. This function should
|
||||
/// only be called with ids returned by [`load_image()`](Self::load_image).
|
||||
#[track_caller]
|
||||
pub fn resource<R: 'static>(&self, id: ResourceId) -> &R {
|
||||
self.resources[id.0 as usize]
|
||||
.downcast_ref()
|
||||
.expect("bad resource type")
|
||||
pub fn image(&self, id: ImageId) -> &Image {
|
||||
&self.images[id.0 as usize]
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
/// Descriptions of all font faces this loader serves.
|
||||
fn faces(&self) -> &[FaceInfo];
|
||||
@ -176,8 +194,8 @@ pub trait Loader {
|
||||
/// Load the font face with the given index in [`faces()`](Self::faces).
|
||||
fn load_face(&mut self, idx: usize) -> Option<Buffer>;
|
||||
|
||||
/// Load a file from a URL.
|
||||
fn load_file(&mut self, url: &str) -> Option<Buffer>;
|
||||
/// Load a file from a path.
|
||||
fn load_file(&mut self, path: &str) -> Option<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)]
|
||||
pub struct ResourceId(u32);
|
||||
pub struct ImageId(u32);
|
||||
|
||||
impl ResourceId {
|
||||
/// A blank initialization value.
|
||||
pub const MAX: Self = Self(u32::MAX);
|
||||
impl ImageId {
|
||||
/// Create an image id from the raw underlying value.
|
||||
///
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::color::Color;
|
||||
use crate::env::{FaceId, ResourceId};
|
||||
use crate::env::{FaceId, ImageId};
|
||||
use crate::geom::{Length, Path, Point, Size};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -41,9 +41,9 @@ pub enum Element {
|
||||
/// Shaped text.
|
||||
Text(Text),
|
||||
/// A geometric shape.
|
||||
Geometry(Geometry),
|
||||
Geometry(Shape, Fill),
|
||||
/// A raster image.
|
||||
Image(Image),
|
||||
Image(ImageId, Size),
|
||||
}
|
||||
|
||||
/// A run of shaped text.
|
||||
@ -54,7 +54,7 @@ pub struct Text {
|
||||
/// The font size.
|
||||
pub size: Length,
|
||||
/// The glyph fill color / texture.
|
||||
pub color: Fill,
|
||||
pub fill: Fill,
|
||||
/// The glyphs.
|
||||
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.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Shape {
|
||||
@ -113,12 +100,3 @@ pub enum Fill {
|
||||
/// The fill is a 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,
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ impl<'a> ShapedText<'a> {
|
||||
let mut text = Text {
|
||||
face_id,
|
||||
size: self.props.size,
|
||||
color: self.props.color,
|
||||
fill: self.props.color,
|
||||
glyphs: vec![],
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
use ::image::GenericImageView;
|
||||
|
||||
use super::*;
|
||||
use crate::env::{ImageResource, ResourceId};
|
||||
use crate::layout::{AnyNode, Areas, Element, Frame, Image, Layout, LayoutContext};
|
||||
use crate::env::ImageId;
|
||||
use crate::layout::{AnyNode, Areas, Element, Frame, Layout, LayoutContext};
|
||||
|
||||
/// `image`: An image.
|
||||
///
|
||||
@ -20,9 +20,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
|
||||
Value::template("image", move |ctx| {
|
||||
if let Some(path) = &path {
|
||||
let loaded = ctx.env.load_resource(&path.v, ImageResource::parse);
|
||||
if let Some(id) = loaded {
|
||||
let img = ctx.env.resource::<ImageResource>(id);
|
||||
if let Some(id) = ctx.env.load_image(&path.v) {
|
||||
let img = ctx.env.image(id);
|
||||
let dimensions = img.buf.dimensions();
|
||||
ctx.push(ImageNode { id, dimensions, width, height });
|
||||
} else {
|
||||
@ -35,8 +34,8 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
|
||||
/// An image node.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
struct ImageNode {
|
||||
/// The resource id of the image file.
|
||||
id: ResourceId,
|
||||
/// The id of the image file.
|
||||
id: ImageId,
|
||||
/// The pixel dimensions of the image.
|
||||
dimensions: (u32, u32),
|
||||
/// The fixed width, if any.
|
||||
@ -75,7 +74,7 @@ impl Layout for ImageNode {
|
||||
};
|
||||
|
||||
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]
|
||||
}
|
||||
|
@ -13,10 +13,10 @@ use pdf_writer::{
|
||||
use ttf_parser::{name_id, GlyphId};
|
||||
|
||||
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::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.
|
||||
///
|
||||
@ -35,7 +35,7 @@ struct PdfExporter<'a> {
|
||||
env: &'a Env,
|
||||
refs: Refs,
|
||||
fonts: Remapper<FaceId>,
|
||||
images: Remapper<ResourceId>,
|
||||
images: Remapper<ImageId>,
|
||||
}
|
||||
|
||||
impl<'a> PdfExporter<'a> {
|
||||
@ -49,16 +49,16 @@ impl<'a> PdfExporter<'a> {
|
||||
|
||||
for frame in frames {
|
||||
for (_, element) in &frame.elements {
|
||||
match element {
|
||||
Element::Text(shaped) => fonts.insert(shaped.face_id),
|
||||
Element::Image(image) => {
|
||||
let img = env.resource::<ImageResource>(image.id);
|
||||
match *element {
|
||||
Element::Text(ref shaped) => fonts.insert(shaped.face_id),
|
||||
Element::Geometry(_, _) => {}
|
||||
Element::Image(id, _) => {
|
||||
let img = env.image(id);
|
||||
if img.buf.color().has_alpha() {
|
||||
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 y = (page.size.height - pos.y).to_pt() as f32;
|
||||
|
||||
match element {
|
||||
&Element::Image(Image { id, size: Size { width, height } }) => {
|
||||
let name = format!("Im{}", self.images.map(id));
|
||||
let w = width.to_pt() as f32;
|
||||
let h = height.to_pt() as f32;
|
||||
match *element {
|
||||
Element::Text(ref shaped) => {
|
||||
if fill != Some(shaped.fill) {
|
||||
write_fill(&mut content, shaped.fill);
|
||||
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();
|
||||
let mut text = content.text();
|
||||
|
||||
// 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(geometry) => {
|
||||
Element::Geometry(ref shape, fill) => {
|
||||
content.save_state();
|
||||
write_fill(&mut content, geometry.fill);
|
||||
write_fill(&mut content, fill);
|
||||
|
||||
match geometry.shape {
|
||||
match *shape {
|
||||
Shape::Rect(Size { width, height }) => {
|
||||
let w = width.to_pt() as f32;
|
||||
let h = height.to_pt() as f32;
|
||||
@ -177,27 +189,15 @@ impl<'a> PdfExporter<'a> {
|
||||
content.restore_state();
|
||||
}
|
||||
|
||||
Element::Text(shaped) => {
|
||||
if fill != Some(shaped.color) {
|
||||
write_fill(&mut content, shaped.color);
|
||||
fill = Some(shaped.color);
|
||||
}
|
||||
Element::Image(id, Size { width, height }) => {
|
||||
let name = format!("Im{}", self.images.map(id));
|
||||
let w = width.to_pt() as f32;
|
||||
let h = height.to_pt() as f32;
|
||||
|
||||
let mut text = content.text();
|
||||
|
||||
// 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());
|
||||
content.save_state();
|
||||
content.matrix(w, 0.0, 0.0, h, x, y - h);
|
||||
content.x_object(Name(name.as_bytes()));
|
||||
content.restore_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,8 +311,8 @@ impl<'a> PdfExporter<'a> {
|
||||
fn write_images(&mut self) {
|
||||
let mut masks_seen = 0;
|
||||
|
||||
for (id, resource) in self.refs.images().zip(self.images.layout_indices()) {
|
||||
let img = self.env.resource::<ImageResource>(resource);
|
||||
for (id, image_id) in self.refs.images().zip(self.images.layout_indices()) {
|
||||
let img = self.env.image(image_id);
|
||||
let (width, height) = img.buf.dimensions();
|
||||
|
||||
// Add the primary image.
|
||||
@ -397,7 +397,7 @@ const DEFLATE_LEVEL: u8 = 6;
|
||||
/// Encode an image with a suitable filter.
|
||||
///
|
||||
/// 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 (filter, space) = match (img.format, &img.buf) {
|
||||
// 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.
|
||||
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 data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL);
|
||||
(data, Filter::FlateDecode)
|
||||
|
@ -15,11 +15,11 @@ use walkdir::WalkDir;
|
||||
|
||||
use typst::color;
|
||||
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::exec::State;
|
||||
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::parse::{LineMap, Scanner};
|
||||
use typst::pdf;
|
||||
@ -385,15 +385,21 @@ fn draw(env: &Env, frames: &[Frame], dpi: f32) -> Pixmap {
|
||||
None,
|
||||
);
|
||||
|
||||
for &(pos, ref element) in &frame.elements {
|
||||
let pos = origin + pos;
|
||||
let x = pos.x.to_pt() as f32;
|
||||
let y = pos.y.to_pt() as f32;
|
||||
for (pos, element) in &frame.elements {
|
||||
let global = origin + *pos;
|
||||
let x = global.x.to_pt() as f32;
|
||||
let y = global.y.to_pt() as f32;
|
||||
let ts = ts.pre_translate(x, y);
|
||||
match element {
|
||||
Element::Text(shaped) => draw_text(&mut canvas, env, ts, shaped),
|
||||
Element::Image(image) => draw_image(&mut canvas, env, ts, image),
|
||||
Element::Geometry(geom) => draw_geometry(&mut canvas, ts, geom),
|
||||
match *element {
|
||||
Element::Text(ref text) => {
|
||||
draw_text(&mut canvas, env, ts, text);
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, shaped: &Text) {
|
||||
let ttf = env.face(shaped.face_id).ttf();
|
||||
fn draw_text(canvas: &mut Pixmap, env: &Env, ts: Transform, text: &Text) {
|
||||
let ttf = env.face(text.face_id).ttf();
|
||||
let mut x = 0.0;
|
||||
|
||||
for glyph in &shaped.glyphs {
|
||||
for glyph in &text.glyphs {
|
||||
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 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() {
|
||||
let path = builder.0.finish().unwrap();
|
||||
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;
|
||||
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) {
|
||||
let paint = convert_typst_fill(element.fill);
|
||||
fn draw_geometry(canvas: &mut Pixmap, ts: Transform, shape: &Shape, fill: Fill) {
|
||||
let paint = convert_typst_fill(fill);
|
||||
let rule = FillRule::default();
|
||||
|
||||
match element.shape {
|
||||
match *shape {
|
||||
Shape::Rect(Size { width, height }) => {
|
||||
let w = width.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) {
|
||||
let img = &env.resource::<ImageResource>(element.id);
|
||||
fn draw_image(canvas: &mut Pixmap, env: &Env, ts: Transform, id: ImageId, size: Size) {
|
||||
let img = env.image(id);
|
||||
|
||||
let mut pixmap = Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
|
||||
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();
|
||||
}
|
||||
|
||||
let view_width = element.size.width.to_pt() as f32;
|
||||
let view_height = element.size.height.to_pt() as f32;
|
||||
let view_width = size.width.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_y = view_height as f32 / pixmap.height() as f32;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user