mirror of
https://github.com/typst/typst
synced 2025-05-21 12:35:29 +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 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
33
src/env/image.rs
vendored
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
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
122
src/env/mod.rs
vendored
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
|
@ -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![],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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]
|
||||||
}
|
}
|
||||||
|
@ -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();
|
let mut text = content.text();
|
||||||
content.matrix(w, 0.0, 0.0, h, x, y - h);
|
|
||||||
content.x_object(Name(name.as_bytes()));
|
// Then, also check if we need to issue a font switching
|
||||||
content.restore_state();
|
// 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();
|
content.save_state();
|
||||||
write_fill(&mut content, geometry.fill);
|
write_fill(&mut content, fill);
|
||||||
|
|
||||||
match geometry.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;
|
||||||
@ -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)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user