Skip to content

Commit 43aa468

Browse files
authored
Switch to Result-based API. (#75)
1 parent 5779739 commit 43aa468

14 files changed

Lines changed: 215 additions & 107 deletions

File tree

cli/src/convert.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ pub fn convert_(
3939

4040
let tree = usvg::Tree::from_str(&svg, &options).map_err(|err| err.to_string())?;
4141

42-
let pdf = svg2pdf::to_pdf(&tree, conversion_options, page_options);
42+
let pdf = svg2pdf::to_pdf(&tree, conversion_options, page_options)
43+
.map_err(|e| format!("Failed to convert PDF file: {e}"))?;
4344

4445
std::fs::write(output, pdf).map_err(|_| "Failed to write PDF file")?;
4546

src/lib.rs

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ let mut options = svg2pdf::usvg::Options::default();
2626
options.fontdb_mut().load_system_fonts();
2727
let tree = svg2pdf::usvg::Tree::from_str(&svg, &options)?;
2828
29-
let pdf = svg2pdf::to_pdf(&tree, ConversionOptions::default(), PageOptions::default());
29+
let pdf = svg2pdf::to_pdf(&tree, ConversionOptions::default(), PageOptions::default()).unwrap();
3030
std::fs::write(output, pdf)?;
3131
# Ok(()) }
3232
```
@@ -56,8 +56,11 @@ comprehensive list.
5656
mod render;
5757
mod util;
5858

59+
use std::fmt;
60+
use std::fmt::{Display, Formatter};
5961
pub use usvg;
6062

63+
use crate::ConversionError::UnknownError;
6164
use once_cell::sync::Lazy;
6265
use pdf_writer::{Chunk, Content, Filter, Finish, Pdf, Ref, TextStr};
6366
use usvg::{Size, Transform, Tree};
@@ -88,6 +91,38 @@ impl Default for PageOptions {
8891
}
8992
}
9093

94+
/// A error that can appear during conversion.
95+
#[derive(Copy, Clone, Debug)]
96+
pub enum ConversionError {
97+
/// The SVG image contains an unrecognized type of image.
98+
InvalidImage,
99+
/// An unknown error occurred during the conversion. This could indicate a bug in the
100+
/// svg2pdf.
101+
UnknownError,
102+
/// An error occurred while subsetting a font.
103+
#[cfg(feature = "text")]
104+
SubsetError(fontdb::ID),
105+
/// An error occurred while reading a font.
106+
#[cfg(feature = "text")]
107+
InvalidFont(fontdb::ID),
108+
}
109+
110+
impl Display for ConversionError {
111+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
112+
match self {
113+
Self::InvalidImage => f.write_str("An unknown type of image appears in the SVG."),
114+
Self::UnknownError => f.write_str("An unknown error occurred during the conversion. This could indicate a bug in svg2pdf"),
115+
#[cfg(feature = "text")]
116+
Self::SubsetError(_) => f.write_str("An error occurred while subsetting a font."),
117+
#[cfg(feature = "text")]
118+
Self::InvalidFont(_) => f.write_str("An error occurred while reading a font."),
119+
}
120+
}
121+
}
122+
123+
/// The result type for everything.
124+
type Result<T> = std::result::Result<T, ConversionError>;
125+
91126
/// Options for the PDF conversion.
92127
#[derive(Copy, Clone)]
93128
pub struct ConversionOptions {
@@ -146,23 +181,23 @@ impl Default for ConversionOptions {
146181
/// let mut tree = svg2pdf::usvg::Tree::from_str(&svg, &options)?;
147182
///
148183
///
149-
/// let pdf = svg2pdf::to_pdf(&tree, ConversionOptions::default(), PageOptions::default());
184+
/// let pdf = svg2pdf::to_pdf(&tree, ConversionOptions::default(), PageOptions::default()).unwrap();
150185
/// std::fs::write(output, pdf)?;
151186
/// # Ok(()) }
152187
/// ```
153188
pub fn to_pdf(
154189
tree: &Tree,
155190
conversion_options: ConversionOptions,
156191
page_options: PageOptions,
157-
) -> Vec<u8> {
192+
) -> Result<Vec<u8>> {
158193
let mut ctx = Context::new(tree, conversion_options);
159194
let mut pdf = Pdf::new();
160195

161196
let dpi_ratio = 72.0 / page_options.dpi;
162197
let dpi_transform = Transform::from_scale(dpi_ratio, dpi_ratio);
163198
let page_size =
164199
Size::from_wh(tree.size().width() * dpi_ratio, tree.size().height() * dpi_ratio)
165-
.unwrap();
200+
.ok_or(UnknownError)?;
166201

167202
let catalog_ref = ctx.alloc_ref();
168203
let page_tree_ref = ctx.alloc_ref();
@@ -177,7 +212,7 @@ pub fn to_pdf(
177212
let mut content = Content::new();
178213
content.save_state();
179214
content.transform(dpi_transform.to_pdf_transform());
180-
tree_to_stream(tree, &mut pdf, &mut content, &mut ctx, &mut rc);
215+
tree_to_stream(tree, &mut pdf, &mut content, &mut ctx, &mut rc)?;
181216
content.restore_state();
182217
let content_stream = ctx.finish_content(content);
183218
let mut stream = pdf.stream(content_ref, &content_stream);
@@ -203,12 +238,12 @@ pub fn to_pdf(
203238
page.contents(content_ref);
204239
page.finish();
205240

206-
ctx.write_global_objects(&mut pdf);
241+
ctx.write_global_objects(&mut pdf)?;
207242

208243
let document_info_id = ctx.alloc_ref();
209244
pdf.document_info(document_info_id).producer(TextStr("svg2pdf"));
210245

211-
pdf.finish()
246+
Ok(pdf.finish())
212247
}
213248

214249
/// Convert a [Tree] into a [`Chunk`].
@@ -251,7 +286,7 @@ pub fn to_pdf(
251286
/// let mut options = svg2pdf::usvg::Options::default();
252287
/// options.fontdb_mut().load_system_fonts();
253288
/// let tree = svg2pdf::usvg::Tree::from_str(&svg, &options)?;
254-
/// let (mut svg_chunk, svg_id) = svg2pdf::to_chunk(&tree, svg2pdf::ConversionOptions::default());
289+
/// let (mut svg_chunk, svg_id) = svg2pdf::to_chunk(&tree, svg2pdf::ConversionOptions::default()).unwrap();
255290
///
256291
/// // Renumber the chunk so that we can embed it into our existing workflow, and also make sure
257292
/// // to update `svg_id`.
@@ -306,11 +341,14 @@ pub fn to_pdf(
306341
/// std::fs::write("target/embedded.pdf", pdf.finish())?;
307342
/// # Ok(()) }
308343
/// ```
309-
pub fn to_chunk(tree: &Tree, conversion_options: ConversionOptions) -> (Chunk, Ref) {
344+
pub fn to_chunk(
345+
tree: &Tree,
346+
conversion_options: ConversionOptions,
347+
) -> Result<(Chunk, Ref)> {
310348
let mut chunk = Chunk::new();
311349

312350
let mut ctx = Context::new(tree, conversion_options);
313-
let x_ref = tree_to_xobject(tree, &mut chunk, &mut ctx);
314-
ctx.write_global_objects(&mut chunk);
315-
(chunk, x_ref)
351+
let x_ref = tree_to_xobject(tree, &mut chunk, &mut ctx)?;
352+
ctx.write_global_objects(&mut chunk)?;
353+
Ok((chunk, x_ref))
316354
}

src/render/clip_path.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use super::path::draw_path;
88
use crate::util::context::Context;
99
use crate::util::helper::{bbox_to_non_zero_rect, NameExt, RectExt, TransformExt};
1010
use crate::util::resources::ResourceContainer;
11+
use crate::Result;
1112

1213
/// Render a clip path into a content stream.
1314
pub fn render(
@@ -17,7 +18,7 @@ pub fn render(
1718
content: &mut Content,
1819
ctx: &mut Context,
1920
rc: &mut ResourceContainer,
20-
) {
21+
) -> Result<()> {
2122
// Unfortunately, clip paths are a bit tricky to deal with, the reason being that clip paths in
2223
// SVGs can be much more complex than in PDF. In SVG, clip paths can have transforms, as well as
2324
// nested clip paths. The objects inside of the clip path can have transforms as well, making it
@@ -54,10 +55,12 @@ pub fn render(
5455
clip_rules.first().copied().unwrap_or(FillRule::NonZero),
5556
);
5657
} else {
57-
let clip_path_ref = create_complex_clip_path(group, clip_path, chunk, ctx);
58+
let clip_path_ref = create_complex_clip_path(group, clip_path, chunk, ctx)?;
5859
let clip_path_name = rc.add_graphics_state(clip_path_ref);
5960
content.set_parameters(clip_path_name.to_pdf_name());
6061
}
62+
63+
Ok(())
6164
}
6265

6366
fn is_simple_clip_path(group: &Group) -> bool {
@@ -173,15 +176,15 @@ fn create_complex_clip_path(
173176
clip_path: &ClipPath,
174177
chunk: &mut Chunk,
175178
ctx: &mut Context,
176-
) -> Ref {
179+
) -> Result<Ref> {
177180
let mut rc = ResourceContainer::new();
178181
let x_ref = ctx.alloc_ref();
179182

180183
let mut content = Content::new();
181184
content.save_state();
182185

183186
if let Some(clip_path) = clip_path.clip_path() {
184-
render(parent, clip_path, chunk, &mut content, ctx, &mut rc);
187+
render(parent, clip_path, chunk, &mut content, ctx, &mut rc)?;
185188
}
186189

187190
content.transform(clip_path.transform().to_pdf_transform());
@@ -196,7 +199,7 @@ fn create_complex_clip_path(
196199
Transform::default(),
197200
None,
198201
&mut rc,
199-
);
202+
)?;
200203
content.restore_state();
201204

202205
let content_stream = ctx.finish_content(content);
@@ -224,5 +227,5 @@ fn create_complex_clip_path(
224227
let mut gs = chunk.ext_graphics(gs_ref);
225228
gs.soft_mask().subtype(MaskType::Alpha).group(x_ref);
226229

227-
gs_ref
230+
Ok(gs_ref)
228231
}

src/render/filter.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use crate::render::image;
22
use crate::util::context::Context;
33
use crate::util::resources::ResourceContainer;
4+
use crate::ConversionError::UnknownError;
5+
use crate::Result;
46
use pdf_writer::{Chunk, Content};
57
use std::sync::Arc;
68
use tiny_skia::{Size, Transform};
@@ -13,18 +15,23 @@ pub fn render(
1315
content: &mut Content,
1416
ctx: &mut Context,
1517
rc: &mut ResourceContainer,
16-
) -> Option<()> {
18+
) -> Result<()> {
1719
// TODO: Add a check so that huge regions don't crash svg2pdf (see huge-region.svg test case)
18-
let layer_bbox = group.layer_bounding_box().transform(group.transform())?;
20+
let layer_bbox = group
21+
.layer_bounding_box()
22+
.transform(group.transform())
23+
.ok_or(UnknownError)?;
1924
let pixmap_size = Size::from_wh(
2025
layer_bbox.width() * ctx.options.raster_scale,
2126
layer_bbox.height() * ctx.options.raster_scale,
22-
)?;
27+
)
28+
.ok_or(UnknownError)?;
2329

2430
let mut pixmap = tiny_skia::Pixmap::new(
2531
pixmap_size.width().round() as u32,
2632
pixmap_size.height().round() as u32,
27-
)?;
33+
)
34+
.ok_or(UnknownError)?;
2835

2936
let initial_transform =
3037
Transform::from_scale(ctx.options.raster_scale, ctx.options.raster_scale)
@@ -43,7 +50,7 @@ pub fn render(
4350
&mut pixmap.as_mut(),
4451
);
4552

46-
let encoded_image = pixmap.encode_png().ok()?;
53+
let encoded_image = pixmap.encode_png().map_err(|_| UnknownError)?;
4754

4855
image::render(
4956
true,
@@ -53,7 +60,7 @@ pub fn render(
5360
content,
5461
ctx,
5562
rc,
56-
);
63+
)?;
5764

58-
Some(())
65+
Ok(())
5966
}

src/render/group.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::ConversionError::UnknownError;
12
use pdf_writer::{Chunk, Content, Filter, Finish, Ref};
23
use std::ops::Mul;
34
use usvg::{Opacity, Transform};
@@ -8,6 +9,7 @@ use super::{clip_path, mask, Render};
89
use crate::util::context::Context;
910
use crate::util::helper::{BlendModeExt, GroupExt, NameExt, RectExt, TransformExt};
1011
use crate::util::resources::ResourceContainer;
12+
use crate::Result;
1113

1214
/// Render a group into a content stream.
1315
pub fn render(
@@ -18,11 +20,10 @@ pub fn render(
1820
accumulated_transform: Transform,
1921
initial_opacity: Option<Opacity>,
2022
rc: &mut ResourceContainer,
21-
) {
23+
) -> Result<()> {
2224
#[cfg(feature = "filters")]
2325
if !group.filters().is_empty() {
24-
filter::render(group, chunk, content, ctx, rc);
25-
return;
26+
return filter::render(group, chunk, content, ctx, rc);
2627
}
2728

2829
#[cfg(not(feature = "filters"))]
@@ -51,21 +52,25 @@ pub fn render(
5152
// hack of setting and then reversing the transform.
5253
if let Some(mask) = group.mask() {
5354
content.transform(group.transform().to_pdf_transform());
54-
mask::render(group, mask, chunk, content, ctx, rc);
55-
content.transform(group.transform().invert().unwrap().to_pdf_transform());
55+
mask::render(group, mask, chunk, content, ctx, rc)?;
56+
content.transform(
57+
group.transform().invert().ok_or(UnknownError)?.to_pdf_transform(),
58+
);
5659
}
5760

5861
// We don't need to pass the accumulated transform here because if a pattern appears in a
5962
// XObject, it will be mapped to the coordinate space of where the XObject was invoked, meaning
6063
// that it will also be affected by the transforms in the content stream. If we passed on the
6164
// accumulated transform, they would be applied twice.
62-
let x_ref = create_x_object(group, chunk, ctx, Transform::default());
65+
let x_ref = create_x_object(group, chunk, ctx, Transform::default())?;
6366
let x_name = rc.add_x_object(x_ref);
6467
content.x_object(x_name.to_pdf_name());
6568
content.restore_state();
6669
} else {
67-
create_to_stream(group, chunk, content, ctx, accumulated_transform, rc);
70+
create_to_stream(group, chunk, content, ctx, accumulated_transform, rc)?;
6871
}
72+
73+
Ok(())
6974
}
7075

7176
/// Turn a group into an XObject.
@@ -74,7 +79,7 @@ fn create_x_object(
7479
chunk: &mut Chunk,
7580
ctx: &mut Context,
7681
accumulated_transform: Transform,
77-
) -> Ref {
82+
) -> Result<Ref> {
7883
let x_ref = ctx.alloc_ref();
7984
let mut rc = ResourceContainer::new();
8085

@@ -86,7 +91,7 @@ fn create_x_object(
8691

8792
let mut content = Content::new();
8893

89-
create_to_stream(group, chunk, &mut content, ctx, accumulated_transform, &mut rc);
94+
create_to_stream(group, chunk, &mut content, ctx, accumulated_transform, &mut rc)?;
9095

9196
let content_stream = ctx.finish_content(content);
9297

@@ -108,7 +113,7 @@ fn create_x_object(
108113
x_object.bbox(pdf_bbox);
109114
x_object.finish();
110115

111-
x_ref
116+
Ok(x_ref)
112117
}
113118

114119
/// Write a group into a content stream. Opacities will be ignored. If opacities are needed,
@@ -120,18 +125,20 @@ fn create_to_stream(
120125
ctx: &mut Context,
121126
accumulated_transform: Transform,
122127
rc: &mut ResourceContainer,
123-
) {
128+
) -> Result<()> {
124129
content.save_state();
125130
content.transform(group.transform().to_pdf_transform());
126131
let accumulated_transform = accumulated_transform.pre_concat(group.transform());
127132

128133
if let Some(clip_path) = &group.clip_path() {
129-
clip_path::render(group, clip_path, chunk, content, ctx, rc);
134+
clip_path::render(group, clip_path, chunk, content, ctx, rc)?;
130135
}
131136

132137
for child in group.children() {
133-
child.render(chunk, content, ctx, accumulated_transform, rc);
138+
child.render(chunk, content, ctx, accumulated_transform, rc)?;
134139
}
135140

136141
content.restore_state();
142+
143+
Ok(())
137144
}

0 commit comments

Comments
 (0)