diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-12-07 00:10:05 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-12-07 00:10:05 +0100 |
| commit | 1b67887db0c2f2cd17d60d70c83d56e9e81f4a41 (patch) | |
| tree | 7930718d6dbdf92d1feaa92df97830ef3dfc4ed5 | |
| parent | 2982020480fc3d9061e26ca24dd467f315981dff (diff) | |
| parent | 7c829c5c1b67ac8e8fbe4fc4ba01468d100bfb47 (diff) | |
Merge pull request #49 from typst/svg
Add SVG capabilities
| -rw-r--r-- | Cargo.toml | 4 | ||||
| -rw-r--r-- | NOTICE | 6 | ||||
| -rw-r--r-- | src/export/pdf.rs | 94 | ||||
| -rw-r--r-- | src/image.rs | 98 | ||||
| -rw-r--r-- | tests/ref/elements/image.png | bin | 211019 -> 186196 bytes | |||
| -rw-r--r-- | tests/res/monkey.svg | 57 | ||||
| -rw-r--r-- | tests/res/pattern.svg | 22 | ||||
| -rw-r--r-- | tests/typ/elements/image.typ | 8 | ||||
| -rw-r--r-- | tests/typeset.rs | 47 |
9 files changed, 273 insertions, 63 deletions
@@ -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]] @@ -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 Binary files differindex 92c68e29..bdcfd200 100644 --- a/tests/ref/elements/image.png +++ b/tests/ref/elements/image.png 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()) } |
