Add SVG capabilities

This commit is contained in:
Martin Haug 2021-12-06 14:58:57 +01:00
parent 2982020480
commit f15ee7efb6
8 changed files with 280 additions and 60 deletions

View File

@ -23,13 +23,15 @@ fxhash = "0.2.1"
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
itertools = "0.10"
miniz_oxide = "0.4"
pdf-writer = "0.4"
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "e1ec200" }
rustybuzz = "0.4"
serde = { version = "1", features = ["derive", "rc"] }
svg2pdf = { git = "https://github.com/typst/svg2pdf", rev = "a127d6f", 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" }
tiny-skia = "0.6"
usvg = { version = "0.15", default-features = false }
resvg = { version = "0.19", default-features = false, features = ["text"] }
tiny-skia = "0.6.1"
walkdir = "2"
[[bin]]

View File

@ -10,13 +10,14 @@ use pdf_writer::types::{
ActionType, AnnotationType, CidFontType, FontFlags, SystemInfo, UnicodeCmap,
};
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr};
use svg2pdf::{convert_tree_into, Options};
use ttf_parser::{name_id, GlyphId, Tag};
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 +91,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 +219,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);
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();
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 = convert_tree_into(
&img.0,
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 +651,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 +692,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)
}

View File

@ -9,6 +9,7 @@ use std::rc::Rc;
use image::io::Reader as ImageReader;
use image::{DynamicImage, GenericImageView, ImageFormat};
use serde::{Deserialize, Serialize};
use usvg::{Error as USvgError, Tree};
use crate::loading::{FileHash, Loader};
@ -88,14 +89,112 @@ impl ImageStore {
}
/// A loaded image.
pub struct Image {
#[derive(Debug)]
pub enum Image {
Raster(RasterImage),
Svg(Svg),
}
impl Image {
/// Parse an image from raw data. This will prioritize SVG images and then
/// try to decode a supported raster format.
pub fn parse(data: &[u8]) -> io::Result<Self> {
match Svg::parse(data) {
Ok(svg) => Ok(Self::Svg(svg)),
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
Ok(Self::Raster(RasterImage::parse(data)?))
}
Err(e) => Err(e),
}
}
/// 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(),
}
}
pub fn is_vector(&self) -> bool {
match self {
Self::Raster(_) => false,
Self::Svg(_) => true,
}
}
}
/// An SVG image, supported through the usvg crate.
pub struct Svg(pub 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();
let tree = Tree::from_data(data, &usvg_opts.to_ref()).map_err(|e| match e {
USvgError::NotAnUtf8Str => {
io::Error::new(io::ErrorKind::InvalidData, "file is not valid utf-8")
}
USvgError::MalformedGZip => io::Error::new(
io::ErrorKind::InvalidData,
"could not extract gzipped SVG",
),
USvgError::ElementsLimitReached => io::Error::new(
io::ErrorKind::Other,
"SVG file has more than 1 million elements",
),
USvgError::InvalidSize => io::Error::new(
io::ErrorKind::InvalidData,
"SVG width or height not greater than zero",
),
USvgError::ParsingFailed(error) => io::Error::new(
io::ErrorKind::InvalidData,
format!("SVG parsing error: {}", error.to_string()),
),
})?;
Ok(Self(tree))
}
/// 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()
}
}
/// 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.
@ -124,7 +223,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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 182 KiB

57
tests/res/monkey.svg Normal file
View File

@ -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>

After

Width:  |  Height:  |  Size: 4.5 KiB

22
tests/res/pattern.svg Normal file
View File

@ -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>

After

Width:  |  Height:  |  Size: 948 B

View File

@ -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"),
)
---
@ -46,6 +46,10 @@ Stuff
// Test baseline.
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")

View File

@ -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())
}