Skip to content

Commit 3cafa54

Browse files
committed
Add capability to write XObjects into existing file
1 parent 4bbdee2 commit 3cafa54

4 files changed

Lines changed: 119 additions & 84 deletions

File tree

Cargo.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ edition = "2018"
55
license = "MIT OR Apache-2.0"
66

77
[features]
8-
default = ["png", "jpeg"]
8+
default = ["png", "jpeg", "text", "system-fonts"]
99
png = ["image/png"]
1010
jpeg = ["image/jpeg"]
11+
text = ["usvg/text"]
12+
system-fonts = ["usvg/system-fonts", "usvg/memmap-fonts"]
1113

1214
[dependencies]
1315
image = { version = "0.23", default-features = false, optional = true }
1416
miniz_oxide = "0.4"
15-
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "a8880b6" }
16-
usvg = "0.19"
17+
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "141aa01" }
18+
usvg = { version = "0.19", default-features = false }

src/defer.rs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use std::collections::HashMap;
88

99
use pdf_writer::types::{MaskType, ShadingType};
1010
use pdf_writer::writers::{ExtGraphicsState, Resources, ShadingPattern};
11-
use pdf_writer::{Name, Rect, Ref};
11+
use pdf_writer::{Finish, Name, PdfWriter, Rect, Ref};
12+
use usvg::{NodeKind, Tree};
1213

13-
use super::CoordToPdf;
14+
use super::{content_stream, form_xobject, Context, CoordToPdf};
1415
use crate::render::Gradient;
1516

1617
/// A gradient to be written.
@@ -223,3 +224,31 @@ pub fn write_xobjects(pending_xobjects: &[(u32, Ref)], resources: &mut Resources
223224
xobjects.pair(Name(name.as_bytes()), *ref_id);
224225
}
225226
}
227+
228+
/// Write the content streams of the used masks stored in the context to the
229+
/// file.
230+
pub(crate) fn write_masks(tree: &Tree, writer: &mut PdfWriter, ctx: &mut Context) {
231+
for (id, gp) in ctx.pending_groups.clone() {
232+
let mask_node = tree.defs_by_id(&id).unwrap();
233+
let borrowed = mask_node.borrow();
234+
235+
if let NodeKind::Mask(_) = *borrowed {
236+
ctx.push();
237+
ctx.initial_mask = gp.initial_mask;
238+
239+
let content = content_stream(&mask_node, writer, ctx);
240+
241+
let mut group = form_xobject(writer, gp.reference, &content, gp.bbox, true);
242+
243+
if let Some(matrix) = gp.matrix {
244+
group.matrix(matrix);
245+
}
246+
247+
let mut resources = group.resources();
248+
ctx.pop(&mut resources);
249+
resources.finish();
250+
}
251+
}
252+
253+
ctx.initial_mask = None;
254+
}

src/lib.rs

Lines changed: 80 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ impl<'a> Context<'a> {
197197
}
198198
}
199199

200-
/// Convert an SVG source string to a PDF buffer.
200+
/// Convert an SVG source string to a standalone PDF buffer.
201201
///
202202
/// Returns an error if the SVG string is malformed.
203203
pub fn convert_str(src: &str, options: Options) -> Result<Vec<u8>, usvg::Error> {
@@ -210,24 +210,9 @@ pub fn convert_str(src: &str, options: Options) -> Result<Vec<u8>, usvg::Error>
210210
Ok(convert_tree(&tree, options))
211211
}
212212

213-
/// Convert a [`usvg` tree](Tree) to a PDF buffer.
213+
/// Convert a [`usvg` tree](Tree) to a standalone PDF buffer.
214214
pub fn convert_tree(tree: &Tree, options: Options) -> Vec<u8> {
215-
let native_size = tree.svg_node().size;
216-
let viewport = if let Some((width, height)) = options.viewport {
217-
(width, height)
218-
} else {
219-
(native_size.width(), native_size.height())
220-
};
221-
222-
let c = CoordToPdf::new(
223-
viewport,
224-
options.dpi,
225-
tree.svg_node().view_box,
226-
options.aspect,
227-
);
228-
229-
let bbox = Rect::new(0.0, 0.0, c.px_to_pt(viewport.0), c.px_to_pt(viewport.1));
230-
215+
let (c, bbox) = get_sizings(tree, &options);
231216
let mut ctx = Context::new(&tree, &bbox, c);
232217

233218
let mut writer = PdfWriter::new();
@@ -239,45 +224,12 @@ pub fn convert_tree(tree: &Tree, options: Options) -> Vec<u8> {
239224
writer.catalog(catalog_id).pages(page_tree_id);
240225
writer.pages(page_tree_id).count(1).kids([page_id]);
241226

242-
for element in tree.defs().children() {
243-
match *element.borrow() {
244-
NodeKind::LinearGradient(ref lg) => {
245-
register_functions(&mut writer, &mut ctx, &lg.id, &lg.base.stops);
246-
}
247-
NodeKind::RadialGradient(ref rg) => {
248-
register_functions(&mut writer, &mut ctx, &rg.id, &rg.base.stops);
249-
}
250-
_ => {}
251-
}
252-
}
227+
preregister(tree, &mut writer, &mut ctx);
253228

254229
ctx.push();
255230
let content = content_stream(&tree.root(), &mut writer, &mut ctx);
256231

257-
for (id, gp) in ctx.pending_groups.clone() {
258-
let mask_node = tree.defs_by_id(&id).unwrap();
259-
let borrowed = mask_node.borrow();
260-
261-
if let NodeKind::Mask(_) = *borrowed {
262-
ctx.push();
263-
ctx.initial_mask = gp.initial_mask;
264-
265-
let content = content_stream(&mask_node, &mut writer, &mut ctx);
266-
267-
let mut group =
268-
form_xobject(&mut writer, gp.reference, &content, gp.bbox, true);
269-
270-
if let Some(matrix) = gp.matrix {
271-
group.matrix(matrix);
272-
}
273-
274-
let mut resources = group.resources();
275-
ctx.pop(&mut resources);
276-
resources.finish();
277-
}
278-
}
279-
280-
ctx.initial_mask = None;
232+
write_masks(tree, &mut writer, &mut ctx);
281233

282234
let mut page = writer.page(page_id);
283235
page.media_box(bbox);
@@ -296,6 +248,81 @@ pub fn convert_tree(tree: &Tree, options: Options) -> Vec<u8> {
296248
writer.finish()
297249
}
298250

251+
/// Convert a [`usvg` tree](Tree) into a Form XObject that can be used as part
252+
/// of a larger document.
253+
///
254+
/// This method is intended for use in an existing [`PdfWriter`] workflow.
255+
///
256+
/// The resulting object can be used by registering a name and the `id` with a
257+
/// page's [`/XObject`](pdf_writer::writers::Resources::x_objects) resources
258+
/// dictionary and then invoking the [`/Do`](pdf_writer::Content::x_object)
259+
/// operator with the name in the page's content stream.
260+
///
261+
/// As the conversion process may need to create multiple indirect objects in
262+
/// the PDF, this function allocates consecutive IDs starting at `id` for its
263+
/// objects and returns the next available ID for your future writing.
264+
pub fn convert_tree_into(
265+
tree: &Tree,
266+
options: Options,
267+
writer: &mut PdfWriter,
268+
id: Ref,
269+
) -> Ref {
270+
let (c, bbox) = get_sizings(tree, &options);
271+
let mut ctx = Context::new(&tree, &bbox, c);
272+
273+
ctx.next_id = id.get() + 1;
274+
275+
preregister(tree, writer, &mut ctx);
276+
277+
ctx.push();
278+
let content = content_stream(&tree.root(), writer, &mut ctx);
279+
280+
write_masks(tree, writer, &mut ctx);
281+
282+
let mut xobject = writer.form_xobject(id, &content);
283+
xobject.bbox(bbox);
284+
let mut resources = xobject.resources();
285+
ctx.pop(&mut resources);
286+
287+
ctx.alloc_ref()
288+
}
289+
290+
/// Calculates the bounding box and size conversions for an usvg tree.
291+
fn get_sizings(tree: &Tree, options: &Options) -> (CoordToPdf, Rect) {
292+
let native_size = tree.svg_node().size;
293+
let viewport = if let Some((width, height)) = options.viewport {
294+
(width, height)
295+
} else {
296+
(native_size.width(), native_size.height())
297+
};
298+
299+
let c = CoordToPdf::new(
300+
viewport,
301+
options.dpi,
302+
tree.svg_node().view_box,
303+
options.aspect,
304+
);
305+
306+
(
307+
c,
308+
Rect::new(0.0, 0.0, c.px_to_pt(viewport.0), c.px_to_pt(viewport.1)),
309+
)
310+
}
311+
312+
fn preregister(tree: &Tree, writer: &mut PdfWriter, ctx: &mut Context) {
313+
for element in tree.defs().children() {
314+
match *element.borrow() {
315+
NodeKind::LinearGradient(ref lg) => {
316+
register_functions(writer, ctx, &lg.id, &lg.base.stops);
317+
}
318+
NodeKind::RadialGradient(ref rg) => {
319+
register_functions(writer, ctx, &rg.id, &rg.base.stops);
320+
}
321+
_ => {}
322+
}
323+
}
324+
}
325+
299326
/// Write a content stream for a node.
300327
fn content_stream<'a>(
301328
node: &usvg::Node,

src/render.rs

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use pdf_writer::types::{
66
TilingType,
77
};
88
use pdf_writer::writers::Shading;
9-
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, TextStr, Writer};
9+
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Writer};
1010
use usvg::{
1111
Align, AspectRatio, FillRule, ImageKind, LineCap, LineJoin, Node, NodeExt, NodeKind,
1212
Paint, PathSegment, Pattern, Transform, Units, ViewBox, Visibility,
@@ -23,7 +23,7 @@ use super::{
2323
apply_clip_path, apply_mask, content_stream, form_xobject, Context, Options,
2424
RgbaColor, SRGB,
2525
};
26-
use crate::convert_tree;
26+
use crate::convert_tree_into;
2727
use crate::defer::{PendingGS, PendingGradient};
2828
use crate::scale::CoordToPdf;
2929

@@ -681,30 +681,7 @@ impl Render for usvg::Image {
681681
dpi: ctx.c.dpi(),
682682
};
683683

684-
let bytes = convert_tree(tree, opt);
685-
let byte_len = bytes.len();
686-
let compressed = compress_to_vec_zlib(&bytes, 8);
687-
688-
let file_ref = ctx.alloc_ref();
689-
let mut embedded = writer.embedded_file(file_ref, &compressed);
690-
embedded.subtype(Name(b"application/pdf"));
691-
embedded.filter(Filter::FlateDecode);
692-
embedded.params().size(byte_len as i32);
693-
embedded.finish();
694-
695-
writer
696-
.form_xobject(image_ref, &[])
697-
.bbox(Rect::new(
698-
0.0,
699-
0.0,
700-
ctx.c.px_to_pt(rect.x() + rect.width()),
701-
ctx.c.px_to_pt(rect.y() + rect.height()),
702-
))
703-
.reference()
704-
.page_number(0)
705-
.file()
706-
.description(TextStr("Embedded SVG image"))
707-
.embedded_file(file_ref);
684+
ctx.next_id = convert_tree_into(tree, opt, writer, image_ref).get();
708685
}
709686

710687
#[cfg(any(not(feature = "jpeg"), not(feature = "png")))]

0 commit comments

Comments
 (0)