mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Bump pdf-writer, svg2pdf and error messages
Co-Authored-By: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
f15ee7efb6
commit
7c829c5c1b
@ -23,10 +23,10 @@ fxhash = "0.2.1"
|
|||||||
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
image = { version = "0.23", default-features = false, features = ["png", "jpeg"] }
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
miniz_oxide = "0.4"
|
miniz_oxide = "0.4"
|
||||||
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "e1ec200" }
|
pdf-writer = "0.4"
|
||||||
rustybuzz = "0.4"
|
rustybuzz = "0.4"
|
||||||
serde = { version = "1", features = ["derive", "rc"] }
|
serde = { version = "1", features = ["derive", "rc"] }
|
||||||
svg2pdf = { git = "https://github.com/typst/svg2pdf", rev = "a127d6f", default-features = false, features = ["text", "png", "jpeg"] }
|
svg2pdf = { version = "0.1", default-features = false, features = ["text", "png", "jpeg"] }
|
||||||
ttf-parser = "0.12"
|
ttf-parser = "0.12"
|
||||||
unicode-bidi = "0.3.5"
|
unicode-bidi = "0.3.5"
|
||||||
unicode-segmentation = "1.8"
|
unicode-segmentation = "1.8"
|
||||||
@ -46,7 +46,7 @@ walkdir = { version = "2", optional = true }
|
|||||||
filedescriptor = "0.8"
|
filedescriptor = "0.8"
|
||||||
iai = { git = "https://github.com/reknih/iai" }
|
iai = { git = "https://github.com/reknih/iai" }
|
||||||
resvg = { version = "0.19", default-features = false, features = ["text"] }
|
resvg = { version = "0.19", default-features = false, features = ["text"] }
|
||||||
tiny-skia = "0.6.1"
|
tiny-skia = "0.6"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
6
NOTICE
6
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),
|
Copyright (c) 2010, ParaType Ltd. (http://www.paratype.com/public),
|
||||||
with Reserved Font Names "PT Sans" and "ParaType".
|
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
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
|
@ -10,7 +10,6 @@ use pdf_writer::types::{
|
|||||||
ActionType, AnnotationType, CidFontType, FontFlags, SystemInfo, UnicodeCmap,
|
ActionType, AnnotationType, CidFontType, FontFlags, SystemInfo, UnicodeCmap,
|
||||||
};
|
};
|
||||||
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Str, TextStr};
|
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 ttf_parser::{name_id, GlyphId, Tag};
|
||||||
|
|
||||||
use super::subset;
|
use super::subset;
|
||||||
@ -263,9 +262,9 @@ impl<'a> PdfExporter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Image::Svg(img) => {
|
Image::Svg(img) => {
|
||||||
let next_ref = convert_tree_into(
|
let next_ref = svg2pdf::convert_tree_into(
|
||||||
&img.0,
|
&img.0,
|
||||||
Options::default(),
|
svg2pdf::Options::default(),
|
||||||
&mut self.writer,
|
&mut self.writer,
|
||||||
image_ref,
|
image_ref,
|
||||||
);
|
);
|
||||||
|
133
src/image.rs
133
src/image.rs
@ -1,6 +1,7 @@
|
|||||||
//! Image handling.
|
//! Image handling.
|
||||||
|
|
||||||
use std::collections::{hash_map::Entry, HashMap};
|
use std::collections::{hash_map::Entry, HashMap};
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@ -9,7 +10,6 @@ use std::rc::Rc;
|
|||||||
use image::io::Reader as ImageReader;
|
use image::io::Reader as ImageReader;
|
||||||
use image::{DynamicImage, GenericImageView, ImageFormat};
|
use image::{DynamicImage, GenericImageView, ImageFormat};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use usvg::{Error as USvgError, Tree};
|
|
||||||
|
|
||||||
use crate::loading::{FileHash, Loader};
|
use crate::loading::{FileHash, Loader};
|
||||||
|
|
||||||
@ -66,7 +66,8 @@ impl ImageStore {
|
|||||||
Entry::Occupied(entry) => entry.into_mut(),
|
Entry::Occupied(entry) => entry.into_mut(),
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
let buffer = self.loader.load(path)?;
|
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);
|
let id = ImageId(self.images.len() as u32);
|
||||||
if let Some(callback) = &self.on_load {
|
if let Some(callback) = &self.on_load {
|
||||||
callback(id, &image);
|
callback(id, &image);
|
||||||
@ -91,21 +92,32 @@ impl ImageStore {
|
|||||||
/// A loaded image.
|
/// A loaded image.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Image {
|
pub enum Image {
|
||||||
|
/// A pixel raster format, like PNG or JPEG.
|
||||||
Raster(RasterImage),
|
Raster(RasterImage),
|
||||||
|
/// An SVG vector graphic.
|
||||||
Svg(Svg),
|
Svg(Svg),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Image {
|
impl Image {
|
||||||
/// Parse an image from raw data. This will prioritize SVG images and then
|
/// Parse an image from raw data. The file extension is used as a hint for
|
||||||
/// try to decode a supported raster format.
|
/// which error message describes the problem best.
|
||||||
pub fn parse(data: &[u8]) -> io::Result<Self> {
|
pub fn parse(data: &[u8], ext: &str) -> io::Result<Self> {
|
||||||
match Svg::parse(data) {
|
match Svg::parse(data) {
|
||||||
Ok(svg) => Ok(Self::Svg(svg)),
|
Ok(svg) => return Ok(Self::Svg(svg)),
|
||||||
Err(e) if e.kind() == io::ErrorKind::InvalidData => {
|
Err(err) if matches!(ext, "svg" | "svgz") => return Err(err),
|
||||||
Ok(Self::Raster(RasterImage::parse(data)?))
|
Err(_) => {}
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// The width of the image in pixels.
|
||||||
@ -123,67 +135,6 @@ impl Image {
|
|||||||
Self::Svg(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.
|
/// A raster image, supported through the image crate.
|
||||||
@ -201,9 +152,9 @@ impl RasterImage {
|
|||||||
pub fn parse(data: &[u8]) -> io::Result<Self> {
|
pub fn parse(data: &[u8]) -> io::Result<Self> {
|
||||||
let cursor = io::Cursor::new(data);
|
let cursor = io::Cursor::new(data);
|
||||||
let reader = ImageReader::new(cursor).with_guessed_format()?;
|
let reader = ImageReader::new(cursor).with_guessed_format()?;
|
||||||
let format = reader.format().ok_or_else(|| {
|
let format = reader
|
||||||
io::Error::new(io::ErrorKind::InvalidData, "unknown image format")
|
.format()
|
||||||
})?;
|
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
|
||||||
|
|
||||||
let buf = reader
|
let buf = reader
|
||||||
.decode()
|
.decode()
|
||||||
@ -233,3 +184,37 @@ impl Debug for RasterImage {
|
|||||||
.finish()
|
.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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<path class="st3" d="m6.1 26.1-.7-1.8"/>
|
<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="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"/>
|
<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 >
|
<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"/>
|
<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>
|
</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="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"/>
|
||||||
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
@ -1,22 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Adapted from
|
<!-- Adapted from
|
||||||
https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns under
|
https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Patterns under
|
||||||
CC0 / Public Domain Licensing -->
|
CC0 / Public Domain Licensing -->
|
||||||
<svg width="200" height="150" xmlns="http://www.w3.org/2000/svg">
|
<svg width="200" height="150" xmlns="http://www.w3.org/2000/svg">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="Gradient1">
|
<linearGradient id="Gradient1">
|
||||||
<stop offset="5%" stop-color="white"/>
|
<stop offset="5%" stop-color="white"/>
|
||||||
<stop offset="95%" stop-color="blue"/>
|
<stop offset="95%" stop-color="blue"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1">
|
<linearGradient id="Gradient2" x1="0" x2="0" y1="0" y2="1">
|
||||||
<stop offset="5%" stop-color="red"/>
|
<stop offset="5%" stop-color="red"/>
|
||||||
<stop offset="95%" stop-color="orange"/>
|
<stop offset="95%" stop-color="orange"/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
<pattern id="Pattern" x="40" y="10" width="50" height="50" patternUnits="userSpaceOnUse">
|
<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="50" height="50" fill="skyblue"/>
|
||||||
<rect x="0" y="0" width="25" height="25" fill="url(#Gradient2)"/>
|
<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"/>
|
<circle cx="25" cy="25" r="20" fill="url(#Gradient1)" fill-opacity="0.5"/>
|
||||||
</pattern>
|
</pattern>
|
||||||
</defs>
|
</defs>
|
||||||
<rect fill="url(#Pattern)" stroke="black" width="200" height="150"/>
|
<rect fill="url(#Pattern)" stroke="black" width="200" height="150"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 948 B After Width: | Height: | Size: 983 B |
Loading…
x
Reference in New Issue
Block a user