summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2021-12-07 00:10:05 +0100
committerGitHub <noreply@github.com>2021-12-07 00:10:05 +0100
commit1b67887db0c2f2cd17d60d70c83d56e9e81f4a41 (patch)
tree7930718d6dbdf92d1feaa92df97830ef3dfc4ed5
parent2982020480fc3d9061e26ca24dd467f315981dff (diff)
parent7c829c5c1b67ac8e8fbe4fc4ba01468d100bfb47 (diff)
Merge pull request #49 from typst/svg
Add SVG capabilities
-rw-r--r--Cargo.toml4
-rw-r--r--NOTICE6
-rw-r--r--src/export/pdf.rs94
-rw-r--r--src/image.rs98
-rw-r--r--tests/ref/elements/image.pngbin211019 -> 186196 bytes
-rw-r--r--tests/res/monkey.svg57
-rw-r--r--tests/res/pattern.svg22
-rw-r--r--tests/typ/elements/image.typ8
-rw-r--r--tests/typeset.rs47
9 files changed, 273 insertions, 63 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 8d1e7201..6e11cc05 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,10 +26,12 @@ miniz_oxide = "0.4"
pdf-writer = "0.4"
rustybuzz = "0.4"
serde = { version = "1", features = ["derive", "rc"] }
+svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] }
ttf-parser = "0.12"
unicode-bidi = "0.3.5"
unicode-segmentation = "1.8"
unicode-xid = "0.2"
+usvg = { version = "0.19", default-features = false, features = ["text"] }
xi-unicode = "0.3"
anyhow = { version = "1", optional = true }
codespan-reporting = { version = "0.11", optional = true }
@@ -43,8 +45,8 @@ walkdir = { version = "2", optional = true }
[dev-dependencies]
filedescriptor = "0.8"
iai = { git = "https://github.com/reknih/iai" }
+resvg = { version = "0.19", default-features = false, features = ["text"] }
tiny-skia = "0.6"
-usvg = { version = "0.15", default-features = false }
walkdir = "2"
[[bin]]
diff --git a/NOTICE b/NOTICE
index 98177977..8c2d9341 100644
--- a/NOTICE
+++ b/NOTICE
@@ -16,6 +16,12 @@ The SIL Open Font License Version 1.1 applies to:
Copyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public),
with Reserved Font Names "PT Sans" and "ParaType".
+* Monkey emoji in tests/res/monkey.svg
+ Copyright 2018 Vincent Le Moign, Streamline Emoji Project
+ Via Wikimedia Commons
+ (https://commons.wikimedia.org/wiki/File:440-monkey.svg)
+ Partially minified using SVGO
+
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
diff --git a/src/export/pdf.rs b/src/export/pdf.rs
index 8df31660..b5f4c009 100644
--- a/src/export/pdf.rs
+++ b/src/export/pdf.rs
@@ -16,7 +16,7 @@ use super::subset;
use crate::font::{find_name, FaceId, FontStore};
use crate::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
use crate::geom::{self, Color, Em, Length, Paint, Point, Size, Transform};
-use crate::image::{Image, ImageId, ImageStore};
+use crate::image::{Image, ImageId, ImageStore, RasterImage};
use crate::Context;
/// Export a collection of frames into a PDF file.
@@ -90,7 +90,7 @@ impl<'a> PdfExporter<'a> {
let postscript_name = find_name(ttf.names(), name_id::POST_SCRIPT_NAME)
.unwrap_or_else(|| "unknown".to_string());
- let base_font = format!("ABCDEF+{}", postscript_name);
+ let base_font = format_eco!("ABCDEF+{}", postscript_name);
let base_font = Name(base_font.as_bytes());
let cmap_name = Name(b"Custom");
let system_info = SystemInfo {
@@ -218,44 +218,58 @@ impl<'a> PdfExporter<'a> {
let height = img.height();
// Add the primary image.
- if let Ok((data, filter, has_color)) = encode_image(img) {
- let mut image = self.writer.image_xobject(image_ref, &data);
- image.filter(filter);
- image.width(width as i32);
- image.height(height as i32);
- image.bits_per_component(8);
-
- let space = image.color_space();
- if has_color {
- space.device_rgb();
- } else {
- space.device_gray();
+ match img {
+ Image::Raster(img) => {
+ if let Ok((data, filter, has_color)) = encode_image(img) {
+ let mut image = self.writer.image_xobject(image_ref, &data);
+ image.filter(filter);
+ image.width(width as i32);
+ image.height(height as i32);
+ image.bits_per_component(8);
+
+ let space = image.color_space();
+ if has_color {
+ space.device_rgb();
+ } else {
+ space.device_gray();
+ }
+
+ // Add a second gray-scale image containing the alpha values if
+ // this image has an alpha channel.
+ if img.buf.color().has_alpha() {
+ let (alpha_data, alpha_filter) = encode_alpha(img);
+ let mask_ref = self.alloc.bump();
+ image.s_mask(mask_ref);
+ image.finish();
+
+ let mut mask =
+ self.writer.image_xobject(mask_ref, &alpha_data);
+ mask.filter(alpha_filter);
+ mask.width(width as i32);
+ mask.height(height as i32);
+ mask.color_space().device_gray();
+ mask.bits_per_component(8);
+ }
+ } else {
+ // TODO: Warn that image could not be encoded.
+ self.writer
+ .image_xobject(image_ref, &[])
+ .width(0)
+ .height(0)
+ .bits_per_component(1)
+ .color_space()
+ .device_gray();
+ }
}
-
- // Add a second gray-scale image containing the alpha values if
- // this image has an alpha channel.
- if img.buf.color().has_alpha() {
- let (alpha_data, alpha_filter) = encode_alpha(img);
- let mask_ref = self.alloc.bump();
- image.s_mask(mask_ref);
- image.finish();
-
- let mut mask = self.writer.image_xobject(mask_ref, &alpha_data);
- mask.filter(alpha_filter);
- mask.width(width as i32);
- mask.height(height as i32);
- mask.color_space().device_gray();
- mask.bits_per_component(8);
+ Image::Svg(img) => {
+ let next_ref = svg2pdf::convert_tree_into(
+ &img.0,
+ svg2pdf::Options::default(),
+ &mut self.writer,
+ image_ref,
+ );
+ self.alloc = next_ref;
}
- } else {
- // TODO: Warn that image could not be encoded.
- self.writer
- .image_xobject(image_ref, &[])
- .width(0)
- .height(0)
- .bits_per_component(1)
- .color_space()
- .device_gray();
}
}
}
@@ -636,7 +650,7 @@ impl<'a> PageExporter<'a> {
/// whether the image has color.
///
/// Skips the alpha channel as that's encoded separately.
-fn encode_image(img: &Image) -> ImageResult<(Vec<u8>, Filter, bool)> {
+fn encode_image(img: &RasterImage) -> ImageResult<(Vec<u8>, Filter, bool)> {
Ok(match (img.format, &img.buf) {
// 8-bit gray JPEG.
(ImageFormat::Jpeg, DynamicImage::ImageLuma8(_)) => {
@@ -677,7 +691,7 @@ fn encode_image(img: &Image) -> ImageResult<(Vec<u8>, Filter, bool)> {
}
/// Encode an image's alpha channel if present.
-fn encode_alpha(img: &Image) -> (Vec<u8>, Filter) {
+fn encode_alpha(img: &RasterImage) -> (Vec<u8>, Filter) {
let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect();
(deflate(&pixels), Filter::FlateDecode)
}
diff --git a/src/image.rs b/src/image.rs
index 512b24b1..a7b62503 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -1,6 +1,7 @@
//! Image handling.
use std::collections::{hash_map::Entry, HashMap};
+use std::ffi::OsStr;
use std::fmt::{self, Debug, Formatter};
use std::io;
use std::path::Path;
@@ -65,7 +66,8 @@ impl ImageStore {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
let buffer = self.loader.load(path)?;
- let image = Image::parse(&buffer)?;
+ let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
+ let image = Image::parse(&buffer, &ext)?;
let id = ImageId(self.images.len() as u32);
if let Some(callback) = &self.on_load {
callback(id, &image);
@@ -88,23 +90,71 @@ impl ImageStore {
}
/// A loaded image.
-pub struct Image {
+#[derive(Debug)]
+pub enum Image {
+ /// A pixel raster format, like PNG or JPEG.
+ Raster(RasterImage),
+ /// An SVG vector graphic.
+ Svg(Svg),
+}
+
+impl Image {
+ /// Parse an image from raw data. The file extension is used as a hint for
+ /// which error message describes the problem best.
+ pub fn parse(data: &[u8], ext: &str) -> io::Result<Self> {
+ match Svg::parse(data) {
+ Ok(svg) => return Ok(Self::Svg(svg)),
+ Err(err) if matches!(ext, "svg" | "svgz") => return Err(err),
+ Err(_) => {}
+ }
+
+ match RasterImage::parse(data) {
+ Ok(raster) => return Ok(Self::Raster(raster)),
+ Err(err) if matches!(ext, "png" | "jpg" | "jpeg") => return Err(err),
+ Err(_) => {}
+ }
+
+ Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "unknown image format",
+ ))
+ }
+
+ /// The width of the image in pixels.
+ pub fn width(&self) -> u32 {
+ match self {
+ Self::Raster(image) => image.width(),
+ Self::Svg(image) => image.width(),
+ }
+ }
+
+ /// The height of the image in pixels.
+ pub fn height(&self) -> u32 {
+ match self {
+ Self::Raster(image) => image.height(),
+ Self::Svg(image) => image.height(),
+ }
+ }
+}
+
+/// A raster image, supported through the image crate.
+pub struct RasterImage {
/// The original format the image was encoded in.
pub format: ImageFormat,
/// The decoded image.
pub buf: DynamicImage,
}
-impl Image {
+impl RasterImage {
/// Parse an image from raw data in a supported format (PNG or JPEG).
///
/// The image format is determined automatically.
pub fn parse(data: &[u8]) -> io::Result<Self> {
let cursor = io::Cursor::new(data);
let reader = ImageReader::new(cursor).with_guessed_format()?;
- let format = reader.format().ok_or_else(|| {
- io::Error::new(io::ErrorKind::InvalidData, "unknown image format")
- })?;
+ let format = reader
+ .format()
+ .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
let buf = reader
.decode()
@@ -124,7 +174,7 @@ impl Image {
}
}
-impl Debug for Image {
+impl Debug for RasterImage {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.debug_struct("Image")
.field("format", &self.format)
@@ -134,3 +184,37 @@ impl Debug for Image {
.finish()
}
}
+
+/// An SVG image, supported through the usvg crate.
+pub struct Svg(pub usvg::Tree);
+
+impl Svg {
+ /// Parse an SVG file from a data buffer. This also handles `.svgz`
+ /// compressed files.
+ pub fn parse(data: &[u8]) -> io::Result<Self> {
+ let usvg_opts = usvg::Options::default();
+ usvg::Tree::from_data(data, &usvg_opts.to_ref())
+ .map(Self)
+ .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))
+ }
+
+ /// The width of the image in rounded-up nominal SVG pixels.
+ pub fn width(&self) -> u32 {
+ self.0.svg_node().size.width().ceil() as u32
+ }
+
+ /// The height of the image in rounded-up nominal SVG pixels.
+ pub fn height(&self) -> u32 {
+ self.0.svg_node().size.height().ceil() as u32
+ }
+}
+
+impl Debug for Svg {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("Svg")
+ .field("width", &self.0.svg_node().size.width())
+ .field("height", &self.0.svg_node().size.height())
+ .field("viewBox", &self.0.svg_node().view_box)
+ .finish()
+ }
+}
diff --git a/tests/ref/elements/image.png b/tests/ref/elements/image.png
index 92c68e29..bdcfd200 100644
--- a/tests/ref/elements/image.png
+++ b/tests/ref/elements/image.png
Binary files differ
diff --git a/tests/res/monkey.svg b/tests/res/monkey.svg
new file mode 100644
index 00000000..0e8c1124
--- /dev/null
+++ b/tests/res/monkey.svg
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Monkey emoji by Vincent Le Moign of the Streamline Emoji Project. Sourced
+ from [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:440-monkey.svg)
+ on 2021-06-12 and partially minified using SVGO. Used under the Creative
+ Commons Attribution 4.0 International license -->
+<!--Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)-->
+<svg xmlns="http://www.w3.org/2000/svg" version="1.1" x="0" y="0" viewBox="0 0 48 48" style="enable-background:new 0 0 48 48" xml:space="preserve">
+ <style>
+ .st3 {
+ fill: none;
+ stroke: #45413c;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+ stroke-miterlimit: 10
+ }
+ .st8 {
+ fill: #fff48c
+ }
+ .st34 {
+ fill: #bf8256
+ }
+ .st35 {
+ fill: #dea47a
+ }
+ .st84 {
+ fill: #45413c
+ }
+ </style>
+ <g>
+ <ellipse cx="20.5" cy="44.5" rx="18" ry="1.5" style="opacity:.15;fill:#45413c"/>
+ <path d="M8.7 28.8 8 27.1c-.3-.7-.9-1.1-1.6-1-.9.1-1.6.9-1.5 1.9.7 5-.6 6.6-3 9.4-.5.5-.5 1.3-.1 1.9.4.6 1 .8 1.9.6 4.5-1.6 6.9-6.6 5-11.1z" style="fill:#ffe500"/>
+ <g>
+ <path class="st8" d="M5 29.6c.2-.6.8-1 1.4-1 .7-.1 1.4.4 1.6 1l.7 1.8c.3.6.5 1.1.6 1.7.2-1.4 0-2.9-.6-4.3L8 27.1c-.3-.7-.9-1.1-1.6-1-.9.1-1.6.9-1.5 1.9 0 .5.1 1.1.1 1.6zM2.1 39.6c2.1-2.5 3.2-4.1 2.8-8.3-.2 2.6-1.3 4-3.1 6-.5.5-.5 1.3-.1 1.9l.4.4z"/>
+ </g>
+ <path class="st3" d="M8.7 28.8 8 27.1c-.3-.7-.9-1.1-1.6-1-.9.1-1.6.9-1.5 1.9.7 5-.6 6.6-3 9.4-.5.5-.5 1.3-.1 1.9.4.6 1 .8 1.9.6 4.5-1.6 6.9-6.6 5-11.1z"/>
+ <path class="st3" d="m6.1 26.1-.7-1.8"/>
+ <path class="st3" d="m7.1 23.6-3.5 1.5"/>
+ <path class="st34" d="M30.7 19.4c-.5.9-.2 2.1.7 2.6.9.5 2.1.2 2.6-.7.9-1.7 3-2.3 4.7-1.6 3.5 1.5 5.4 5.4 4.4 9.1-1.7 6.7-7.7 11.3-14.6 11.3h-8l-.6 3.9h8.8c8.7 0 16.4-5.9 18.5-14.4 1.3-5.4-1.3-10.9-6.3-13.3-3.7-1.8-8.1-.5-10.2 3.1z"/>
+ <g>
+ <path class="st35" d="M43.4 27.9c0 .3-.1.6-.2.8C41.5 35.4 35.5 40 28.6 40h-8l-.4 2.2h8.4c6.9 0 12.9-4.6 14.6-11.3.3-1 .3-2 .2-3zM30.7 19.4c-.4.7-.3 1.5.1 2.1 2.1-3.4 6.5-4.7 10.1-3 3.8 1.8 6.2 5.4 6.6 9.4.4-4.8-2.1-9.4-6.6-11.6-3.7-1.8-8.1-.5-10.2 3.1z"/>
+ </g>
+ <path class="st3" d="M30.7 19.4c-.5.9-.2 2.1.7 2.6.9.5 2.1.2 2.6-.7.9-1.7 3-2.3 4.7-1.6 3.5 1.5 5.4 5.4 4.4 9.1-1.7 6.7-7.7 11.3-14.6 11.3h-8l-.6 3.9h8.8c8.7 0 16.4-5.9 18.5-14.4 1.3-5.4-1.3-10.9-6.3-13.3-3.7-1.8-8.1-.5-10.2 3.1z"/>
+ <path class="st34" d="M31.2 30.6c-1.6-8.2-8.8-14.2-17.2-14.2v12l-4.3-.8c-1.6-.3-2.9 1.2-2.4 2.7.2.8.9 1.4 1.7 1.5l5 .8V35c-1 1-1.6 2.5-1.5 4.1l.1 1h-.8c-1.7 0-3 1.6-2.7 3.2.1.5.5.8 1 .8h14.1c4.2 0 7.6-3.4 7.6-7.6-.1-2-.3-4-.6-5.9z"/>
+ <path d="M14 26.3c4.5 0 8.4-2.7 10.2-6.5-2.9-2.1-6.4-3.3-10.2-3.3v9.8z" style="fill:#915e3a"/>
+ <path class="st3" d="M31.2 30.6c-1.6-8.2-8.8-14.2-17.2-14.2v12l-4.3-.8c-1.6-.3-2.9 1.2-2.4 2.7.2.8.9 1.4 1.7 1.5l5 .8V35c-1 1-1.6 2.5-1.5 4.1l.1 1h-.8c-1.7 0-3 1.6-2.7 3.2.1.5.5.8 1 .8h14.1c4.2 0 7.6-3.4 7.6-7.6-.1-2-.3-4-.6-5.9z"/>
+ <path class="st34" d="M22.5 9.2h-.2C21.4 5.4 18 2.6 14 2.6S6.5 5.4 5.7 9.2h-.2c-1.8 0-3.3 1.5-3.3 3.3v.7c0 1.8 1.5 3.3 3.3 3.3h.2C6.5 20.2 9.9 23 14 23s7.4-2.8 8.3-6.6h.2c1.8 0 3.3-1.5 3.3-3.3v-.7c0-1.8-1.5-3.2-3.3-3.2z"/>
+ <path class="st35" d="M22.5 9.2h-.2C21.4 5.4 18 2.6 14 2.6c-4 0-7.4 2.8-8.3 6.6h-.2c-1.8 0-3.3 1.5-3.3 3.3v.7c0 .4.1.7.2 1 .4-1.3 1.7-2.3 3.1-2.3h.2c.8-3.8 4.2-6.6 8.3-6.6 4 0 7.4 2.8 8.3 6.6h.2c1.5 0 2.7 1 3.1 2.3.1-.3.2-.7.2-1v-.7c0-1.9-1.5-3.3-3.3-3.3z"/>
+ <path class="st3" d="M22.5 9.2h-.2C21.4 5.4 18 2.6 14 2.6S6.5 5.4 5.7 9.2h-.2c-1.8 0-3.3 1.5-3.3 3.3v.7c0 1.8 1.5 3.3 3.3 3.3h.2C6.5 20.2 9.9 23 14 23s7.4-2.8 8.3-6.6h.2c1.8 0 3.3-1.5 3.3-3.3v-.7c0-1.8-1.5-3.2-3.3-3.2z"/>
+ <path d="M18.6 15.1c1.1 1 1.7 2.4 1.7 4 0 .5-.1.9-.2 1.3C18.5 22 16.4 23 14 23c-2.8 0-5.2-1.3-6.8-3.3 0 0 0 0 0 0 0-.2-.1-.4-.1-.6 0-1.5.7-2.9 1.7-4-1-.7-1.7-1.9-1.7-3.3 0-2.2 1.8-3.9 3.9-3.9 1 0 1.9.4 2.6 1 .7-.6 1.6-1 2.6-1 2.2 0 3.9 1.8 3.9 3.9.2 1.4-.5 2.6-1.5 3.3z" style="fill:#ffdcd1;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"/>
+ <circle transform="matrix(.05447 -.9985 .9985 .05447 4.528 28.393)" class="st84" cx="17.3" cy="11.8" r="1.3"/>
+ <circle transform="matrix(.05447 -.9985 .9985 .05447 -1.682 21.835)" class="st84" cx="10.7" cy="11.8" r="1.3"/>
+ <path class="st3" d="M11.3 18.4s1.1 1.3 2.6 1.3c1.6 0 2.6-1.3 2.6-1.3"/>
+ <path class="st3" d="m14 28.4 3.5.6"/>
+ <path class="st3" d="m21.4 25 1.6 4.4c.8 2.2-1.1 4.4-3.3 4l-5.7-.8"/>
+ <path class="st3" d="M22.9 39.3c.2-3.2-2.4-5.9-5.7-5.8-1.3.1-2.4.6-3.2 1.5"/>
+ </g>
+</svg>
diff --git a/tests/res/pattern.svg b/tests/res/pattern.svg
new file mode 100644
index 00000000..0a46c0b6
--- /dev/null
+++ b/tests/res/pattern.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Adapted from
+ https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns under
+ CC0 / Public Domain Licensing -->
+<svg width="200" height="150" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <linearGradient id="Gradient1">
+ <stop offset="5%" stop-color="white"/>
+ <stop offset="95%" stop-color="blue"/>
+ </linearGradient>
+ <linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1">
+ <stop offset="5%" stop-color="red"/>
+ <stop offset="95%" stop-color="orange"/>
+ </linearGradient>
+ <pattern id="Pattern" x="40" y="10" width="50" height="50" patternUnits="userSpaceOnUse">
+ <rect x="0" y="0" width="50" height="50" fill="skyblue"/>
+ <rect x="0" y="0" width="25" height="25" fill="url(#Gradient2)"/>
+ <circle cx="25" cy="25" r="20" fill="url(#Gradient1)" fill-opacity="0.5"/>
+ </pattern>
+ </defs>
+ <rect fill="url(#Pattern)" stroke="black" width="200" height="150"/>
+</svg>
diff --git a/tests/typ/elements/image.typ b/tests/typ/elements/image.typ
index 5fd121e3..7fddb12d 100644
--- a/tests/typ/elements/image.typ
+++ b/tests/typ/elements/image.typ
@@ -18,7 +18,7 @@
#image("../../res/rhino.png", height: 30pt)
// Set width and height explicitly and force stretching.
-#image("../../res/tiger.jpg", width: 100%, height: 20pt, fit: "stretch")
+#image("../../res/monkey.svg", width: 100%, height: 20pt, fit: "stretch")
// Make sure the bounding-box of the image is correct.
#align(bottom + right, image("../../res/tiger.jpg", width: 40pt))
@@ -32,7 +32,7 @@
gutter: 3pt,
image("../../res/tiger.jpg", width: 100%, height: 100%, fit: "contain"),
image("../../res/tiger.jpg", width: 100%, height: 100%, fit: "cover"),
- image("../../res/tiger.jpg", width: 100%, height: 100%, fit: "stretch"),
+ image("../../res/monkey.svg", width: 100%, height: 100%, fit: "stretch"),
)
---
@@ -47,6 +47,10 @@ Stuff
A #image("../../res/tiger.jpg", height: 1cm, width: 80%) B
---
+// Test advanced SVG features.
+#image("../../res/pattern.svg")
+
+---
// Error: 8-29 file not found
#image("path/does/not/exist")
diff --git a/tests/typeset.rs b/tests/typeset.rs
index 3011a351..792251c3 100644
--- a/tests/typeset.rs
+++ b/tests/typeset.rs
@@ -8,6 +8,7 @@ use filedescriptor::{FileDescriptor, StdioDescriptor::*};
use image::{GenericImageView, Rgba};
use tiny_skia as sk;
use ttf_parser::{GlyphId, OutlineBuilder};
+use usvg::FitTo;
use walkdir::WalkDir;
use typst::diag::Error;
@@ -17,7 +18,7 @@ use typst::frame::{Element, Frame, Geometry, Group, Shape, Stroke, Text};
use typst::geom::{
self, Color, Length, Paint, PathElement, RgbaColor, Sides, Size, Transform,
};
-use typst::image::Image;
+use typst::image::{Image, RasterImage, Svg};
use typst::layout::layout;
#[cfg(feature = "layout-cache")]
use typst::library::DocumentNode;
@@ -513,7 +514,9 @@ fn draw_text(
let viewbox = format!("viewBox=\"0 0 {0} {0}\" xmlns", units_per_em);
svg.replace("xmlns", &viewbox)
})
- .and_then(|s| usvg::Tree::from_str(&s, &usvg::Options::default()).ok())
+ .and_then(|s| {
+ usvg::Tree::from_str(&s, &usvg::Options::default().to_ref()).ok()
+ })
{
for child in tree.root().children() {
if let usvg::NodeKind::Path(node) = &*child.borrow() {
@@ -535,13 +538,13 @@ fn draw_text(
// TODO: Vertical alignment isn't quite right for Apple Color Emoji,
// and maybe also for Noto Color Emoji. And: Is the size calculation
// correct?
- let img = Image::parse(&raster.data).unwrap();
+ let img = RasterImage::parse(&raster.data).unwrap();
let h = text.size;
let w = (img.width() as f64 / img.height() as f64) * h;
let dx = (raster.x as f32) / (img.width() as f32) * size;
let dy = (raster.y as f32) / (img.height() as f32) * size;
let ts = ts.pre_translate(dx, -size - dy);
- draw_image(canvas, ts, mask, &img, Size::new(w, h));
+ draw_image(canvas, ts, mask, &Image::Raster(img), Size::new(w, h));
} else {
// Otherwise, draw normal outline.
let mut builder = WrappedPathBuilder(sk::PathBuilder::new());
@@ -608,16 +611,34 @@ fn draw_image(
img: &Image,
size: Size,
) {
- let mut pixmap = sk::Pixmap::new(img.buf.width(), img.buf.height()).unwrap();
- for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) {
- let Rgba([r, g, b, a]) = src;
- *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
- }
-
let view_width = size.x.to_f32();
let view_height = size.y.to_f32();
- let scale_x = view_width as f32 / pixmap.width() as f32;
- let scale_y = view_height as f32 / pixmap.height() as f32;
+
+ let pixmap = match img {
+ Image::Raster(img) => {
+ let w = img.buf.width();
+ let h = img.buf.height();
+ let mut pixmap = sk::Pixmap::new(w, h).unwrap();
+ for ((_, _, src), dest) in img.buf.pixels().zip(pixmap.pixels_mut()) {
+ let Rgba([r, g, b, a]) = src;
+ *dest = sk::ColorU8::from_rgba(r, g, b, a).premultiply();
+ }
+ pixmap
+ }
+ Image::Svg(Svg(tree)) => {
+ let size = tree.svg_node().size;
+ let aspect = (size.width() / size.height()) as f32;
+ let scale = ts.sx.max(ts.sy);
+ let w = (scale * view_width.max(aspect * view_height)).ceil() as u32;
+ let h = ((w as f32) / aspect).ceil() as u32;
+ let mut pixmap = sk::Pixmap::new(w, h).unwrap();
+ resvg::render(&tree, FitTo::Size(w, h), pixmap.as_mut());
+ pixmap
+ }
+ };
+
+ let scale_x = view_width / pixmap.width() as f32;
+ let scale_y = view_height / pixmap.height() as f32;
let mut paint = sk::Paint::default();
paint.shader = sk::Pattern::new(
@@ -689,7 +710,7 @@ fn convert_usvg_fill(fill: &usvg::Fill) -> (sk::Paint<'static>, sk::FillRule) {
let mut paint = sk::Paint::default();
paint.anti_alias = true;
- if let usvg::Paint::Color(usvg::Color { red, green, blue }) = fill.paint {
+ if let usvg::Paint::Color(usvg::Color { red, green, blue, alpha: _ }) = fill.paint {
paint.set_color_rgba8(red, green, blue, fill.opacity.to_u8())
}