Skip to content

Commit 2641470

Browse files
committed
Minimum PDF/A support
1 parent 5963e1e commit 2641470

13 files changed

+130
-55
lines changed

Cargo.lock

+2-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ image = { version = "0.25", default-features = false, features = ["jpeg", "png",
2222
miniz_oxide = "0.8"
2323
once_cell = "1.18"
2424
oxipng = { version = "9", default-features = false, features = ["filetime", "parallel", "zopfli"] }
25-
pdf-writer = "0.10"
25+
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "b8f07d6" }
2626
pdfium-render = "=0.8.20"
2727
termcolor = "1.2"
2828
usvg = { version = "0.43", default-features = false }

cli/src/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ fn run() -> Result<(), String> {
2727
compress: true,
2828
embed_text: !args.text_to_paths,
2929
raster_scale: args.raster_scale,
30+
pdfa: false,
3031
};
3132

3233
let page_options = PageOptions { dpi: args.dpi };

src/lib.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ use usvg::{Size, Transform, Tree};
6767

6868
use crate::render::{tree_to_stream, tree_to_xobject};
6969
use crate::util::context::Context;
70-
use crate::util::helper::{deflate, RectExt, TransformExt};
70+
use crate::util::helper::{deflate, ContentExt, RectExt, TransformExt};
7171
use crate::util::resources::ResourceContainer;
7272

7373
// The ICC profiles.
@@ -96,6 +96,11 @@ impl Default for PageOptions {
9696
pub enum ConversionError {
9797
/// The SVG image contains an unrecognized type of image.
9898
InvalidImage,
99+
/// Text shaping resulted in a .notdef glyph. Can only occur if PDF/A
100+
/// processing is enabled.
101+
MissingGlyphs,
102+
/// Converting the SVG would require too much nesting depth.
103+
TooMuchNesting,
99104
/// An unknown error occurred during the conversion. This could indicate a bug in the
100105
/// svg2pdf.
101106
UnknownError,
@@ -111,6 +116,8 @@ impl Display for ConversionError {
111116
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
112117
match self {
113118
Self::InvalidImage => f.write_str("An unknown type of image appears in the SVG."),
119+
Self::MissingGlyphs => f.write_str("A piece of text could not be displayed with any font."),
120+
Self::TooMuchNesting => f.write_str("The SVG's nesting depth is too high."),
114121
Self::UnknownError => f.write_str("An unknown error occurred during the conversion. This could indicate a bug in svg2pdf"),
115122
#[cfg(feature = "text")]
116123
Self::SubsetError(_) => f.write_str("An error occurred while subsetting a font."),
@@ -148,6 +155,9 @@ pub struct ConversionOptions {
148155
///
149156
/// _Default:_ `true`.
150157
pub embed_text: bool,
158+
159+
/// Whether to enforce PDF/A rules.
160+
pub pdfa: bool,
151161
}
152162

153163
impl Default for ConversionOptions {
@@ -156,6 +166,7 @@ impl Default for ConversionOptions {
156166
compress: true,
157167
raster_scale: 1.5,
158168
embed_text: true,
169+
pdfa: false,
159170
}
160171
}
161172
}
@@ -190,7 +201,7 @@ pub fn to_pdf(
190201
conversion_options: ConversionOptions,
191202
page_options: PageOptions,
192203
) -> Result<Vec<u8>> {
193-
let mut ctx = Context::new(tree, conversion_options);
204+
let mut ctx = Context::new(tree, conversion_options)?;
194205
let mut pdf = Pdf::new();
195206

196207
let dpi_ratio = 72.0 / page_options.dpi;
@@ -210,7 +221,7 @@ pub fn to_pdf(
210221
// Generate main content
211222
let mut rc = ResourceContainer::new();
212223
let mut content = Content::new();
213-
content.save_state();
224+
content.save_state_checked()?;
214225
content.transform(dpi_transform.to_pdf_transform());
215226
tree_to_stream(tree, &mut pdf, &mut content, &mut ctx, &mut rc)?;
216227
content.restore_state();
@@ -347,7 +358,7 @@ pub fn to_chunk(
347358
) -> Result<(Chunk, Ref)> {
348359
let mut chunk = Chunk::new();
349360

350-
let mut ctx = Context::new(tree, conversion_options);
361+
let mut ctx = Context::new(tree, conversion_options)?;
351362
let x_ref = tree_to_xobject(tree, &mut chunk, &mut ctx)?;
352363
ctx.write_global_objects(&mut chunk)?;
353364
Ok((chunk, x_ref))

src/render/clip_path.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use usvg::{ClipPath, FillRule, Group, Node, Transform};
66
use super::group;
77
use super::path::draw_path;
88
use crate::util::context::Context;
9-
use crate::util::helper::{bbox_to_non_zero_rect, NameExt, RectExt, TransformExt};
9+
use crate::util::helper::{
10+
bbox_to_non_zero_rect, ContentExt, NameExt, RectExt, TransformExt,
11+
};
1012
use crate::util::resources::ResourceContainer;
1113
use crate::Result;
1214

@@ -181,7 +183,7 @@ fn create_complex_clip_path(
181183
let x_ref = ctx.alloc_ref();
182184

183185
let mut content = Content::new();
184-
content.save_state();
186+
content.save_state_checked()?;
185187

186188
if let Some(clip_path) = clip_path.clip_path() {
187189
render(parent, clip_path, chunk, &mut content, ctx, &mut rc)?;

src/render/group.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ use usvg::{Opacity, Transform};
77
use super::filter;
88
use super::{clip_path, mask, Render};
99
use crate::util::context::Context;
10-
use crate::util::helper::{BlendModeExt, GroupExt, NameExt, RectExt, TransformExt};
10+
use crate::util::helper::{
11+
BlendModeExt, ContentExt, GroupExt, NameExt, RectExt, TransformExt,
12+
};
1113
use crate::util::resources::ResourceContainer;
1214
use crate::Result;
1315

@@ -36,7 +38,7 @@ pub fn render(
3638
let initial_opacity = initial_opacity.unwrap_or(Opacity::ONE);
3739

3840
if group.is_isolated() || initial_opacity.get() != 1.0 {
39-
content.save_state();
41+
content.save_state_checked()?;
4042
let gs_ref = ctx.alloc_ref();
4143
let mut gs = chunk.ext_graphics(gs_ref);
4244
gs.non_stroking_alpha(group.opacity().mul(initial_opacity).get())
@@ -126,7 +128,7 @@ fn create_to_stream(
126128
accumulated_transform: Transform,
127129
rc: &mut ResourceContainer,
128130
) -> Result<()> {
129-
content.save_state();
131+
content.save_state_checked()?;
130132
content.transform(group.transform().to_pdf_transform());
131133
let accumulated_transform = accumulated_transform.pre_concat(group.transform());
132134

src/render/image.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use usvg::{ImageKind, Rect, Size, Transform, Tree};
88

99
use crate::render::tree_to_xobject;
1010
use crate::util::context::Context;
11-
use crate::util::helper::{NameExt, TransformExt};
11+
use crate::util::helper::{ContentExt, NameExt, TransformExt};
1212
use crate::util::resources::ResourceContainer;
1313
use crate::Result;
1414

@@ -59,7 +59,7 @@ pub fn render(
5959
Rect::from_xywh(0.0, 0.0, image_size.width(), image_size.height()).unwrap(),
6060
);
6161

62-
content.save_state();
62+
content.save_state_checked()?;
6363

6464
// Account for the x/y of the viewbox.
6565
content.transform(

src/render/mask.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use usvg::{Group, Mask, Transform};
33

44
use super::group;
55
use crate::util::context::Context;
6-
use crate::util::helper::{clip_to_rect, MaskTypeExt, NameExt, RectExt};
6+
use crate::util::helper::{clip_to_rect, ContentExt, MaskTypeExt, NameExt, RectExt};
77
use crate::util::resources::ResourceContainer;
88
use crate::Result;
99

@@ -34,7 +34,7 @@ pub fn create(
3434
let mut rc = ResourceContainer::new();
3535

3636
let mut content = Content::new();
37-
content.save_state();
37+
content.save_state_checked()?;
3838

3939
if let Some(mask) = mask.mask() {
4040
render(parent, mask, chunk, &mut content, ctx, &mut rc)?;

src/render/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use pdf_writer::{Chunk, Content, Filter, Finish, Ref};
22
use usvg::{Node, Transform, Tree};
33

44
use crate::util::context::Context;
5-
use crate::util::helper::{RectExt, TransformExt};
5+
use crate::util::helper::{ContentExt, RectExt, TransformExt};
66
use crate::util::resources::ResourceContainer;
77
use crate::Result;
88

@@ -28,7 +28,7 @@ pub fn tree_to_stream(
2828
ctx: &mut Context,
2929
rc: &mut ResourceContainer,
3030
) -> Result<()> {
31-
content.save_state();
31+
content.save_state_checked()?;
3232

3333
// From PDF coordinate system to SVG coordinate system
3434
let initial_transform =

src/render/path.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use usvg::{Stroke, Transform};
88

99
use super::{gradient, pattern};
1010
use crate::util::context::Context;
11-
use crate::util::helper::{ColorExt, LineCapExt, LineJoinExt, NameExt};
11+
use crate::util::helper::{ColorExt, ContentExt, LineCapExt, LineJoinExt, NameExt};
1212
use crate::util::resources::ResourceContainer;
1313
use crate::Result;
1414

@@ -104,6 +104,7 @@ pub(crate) fn stroke_path(
104104
let operation = |content: &mut Content, stroke: &Stroke| {
105105
draw_path(path.data().segments(), content);
106106
finish_path(Some(stroke), None, content);
107+
Ok(())
107108
};
108109

109110
if let Some(path_stroke) = path.stroke() {
@@ -131,13 +132,13 @@ pub(crate) fn stroke(
131132
content: &mut Content,
132133
ctx: &mut Context,
133134
rc: &mut ResourceContainer,
134-
operation: impl Fn(&mut Content, &Stroke),
135+
operation: impl Fn(&mut Content, &Stroke) -> Result<()>,
135136
accumulated_transform: Transform,
136137
bbox: Rect,
137138
) -> Result<()> {
138139
let paint = &stroke.paint();
139140

140-
content.save_state();
141+
content.save_state_checked()?;
141142

142143
match paint {
143144
Paint::Color(c) => {
@@ -206,7 +207,7 @@ pub(crate) fn stroke(
206207
content.set_dash_pattern(vec![], 0.0);
207208
}
208209

209-
operation(content, stroke);
210+
operation(content, stroke)?;
210211

211212
content.restore_state();
212213

@@ -229,6 +230,7 @@ pub(crate) fn fill_path(
229230
let operation = |content: &mut Content, fill: &Fill| {
230231
draw_path(path.data().segments(), content);
231232
finish_path(None, Some(fill), content);
233+
Ok(())
232234
};
233235

234236
if let Some(path_fill) = path.fill() {
@@ -256,13 +258,13 @@ pub(crate) fn fill(
256258
content: &mut Content,
257259
ctx: &mut Context,
258260
rc: &mut ResourceContainer,
259-
operation: impl Fn(&mut Content, &Fill),
261+
operation: impl Fn(&mut Content, &Fill) -> Result<()>,
260262
accumulated_transform: Transform,
261263
bbox: Rect,
262264
) -> Result<()> {
263265
let paint = &fill.paint();
264266

265-
content.save_state();
267+
content.save_state_checked()?;
266268

267269
match paint {
268270
Paint::Color(c) => {
@@ -307,7 +309,7 @@ pub(crate) fn fill(
307309
}
308310
}
309311

310-
operation(content, fill);
312+
operation(content, fill)?;
311313
content.restore_state();
312314

313315
Ok(())

0 commit comments

Comments
 (0)