Skip to content

Commit

Permalink
Add capability to write XObjects into existing file
Browse files Browse the repository at this point in the history
  • Loading branch information
reknih committed Dec 5, 2021
1 parent 4bbdee2 commit 3cafa54
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 84 deletions.
8 changes: 5 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ edition = "2018"
license = "MIT OR Apache-2.0"

[features]
default = ["png", "jpeg"]
default = ["png", "jpeg", "text", "system-fonts"]
png = ["image/png"]
jpeg = ["image/jpeg"]
text = ["usvg/text"]
system-fonts = ["usvg/system-fonts", "usvg/memmap-fonts"]

[dependencies]
image = { version = "0.23", default-features = false, optional = true }
miniz_oxide = "0.4"
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "a8880b6" }
usvg = "0.19"
pdf-writer = { git = "https://github.com/typst/pdf-writer", rev = "141aa01" }
usvg = { version = "0.19", default-features = false }
33 changes: 31 additions & 2 deletions src/defer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use std::collections::HashMap;

use pdf_writer::types::{MaskType, ShadingType};
use pdf_writer::writers::{ExtGraphicsState, Resources, ShadingPattern};
use pdf_writer::{Name, Rect, Ref};
use pdf_writer::{Finish, Name, PdfWriter, Rect, Ref};
use usvg::{NodeKind, Tree};

use super::CoordToPdf;
use super::{content_stream, form_xobject, Context, CoordToPdf};
use crate::render::Gradient;

/// A gradient to be written.
Expand Down Expand Up @@ -223,3 +224,31 @@ pub fn write_xobjects(pending_xobjects: &[(u32, Ref)], resources: &mut Resources
xobjects.pair(Name(name.as_bytes()), *ref_id);
}
}

/// Write the content streams of the used masks stored in the context to the
/// file.
pub(crate) fn write_masks(tree: &Tree, writer: &mut PdfWriter, ctx: &mut Context) {
for (id, gp) in ctx.pending_groups.clone() {
let mask_node = tree.defs_by_id(&id).unwrap();
let borrowed = mask_node.borrow();

if let NodeKind::Mask(_) = *borrowed {
ctx.push();
ctx.initial_mask = gp.initial_mask;

let content = content_stream(&mask_node, writer, ctx);

let mut group = form_xobject(writer, gp.reference, &content, gp.bbox, true);

if let Some(matrix) = gp.matrix {
group.matrix(matrix);
}

let mut resources = group.resources();
ctx.pop(&mut resources);
resources.finish();
}
}

ctx.initial_mask = None;
}
133 changes: 80 additions & 53 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ impl<'a> Context<'a> {
}
}

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

/// Convert a [`usvg` tree](Tree) to a PDF buffer.
/// Convert a [`usvg` tree](Tree) to a standalone PDF buffer.
pub fn convert_tree(tree: &Tree, options: Options) -> Vec<u8> {
let native_size = tree.svg_node().size;
let viewport = if let Some((width, height)) = options.viewport {
(width, height)
} else {
(native_size.width(), native_size.height())
};

let c = CoordToPdf::new(
viewport,
options.dpi,
tree.svg_node().view_box,
options.aspect,
);

let bbox = Rect::new(0.0, 0.0, c.px_to_pt(viewport.0), c.px_to_pt(viewport.1));

let (c, bbox) = get_sizings(tree, &options);
let mut ctx = Context::new(&tree, &bbox, c);

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

for element in tree.defs().children() {
match *element.borrow() {
NodeKind::LinearGradient(ref lg) => {
register_functions(&mut writer, &mut ctx, &lg.id, &lg.base.stops);
}
NodeKind::RadialGradient(ref rg) => {
register_functions(&mut writer, &mut ctx, &rg.id, &rg.base.stops);
}
_ => {}
}
}
preregister(tree, &mut writer, &mut ctx);

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

for (id, gp) in ctx.pending_groups.clone() {
let mask_node = tree.defs_by_id(&id).unwrap();
let borrowed = mask_node.borrow();

if let NodeKind::Mask(_) = *borrowed {
ctx.push();
ctx.initial_mask = gp.initial_mask;

let content = content_stream(&mask_node, &mut writer, &mut ctx);

let mut group =
form_xobject(&mut writer, gp.reference, &content, gp.bbox, true);

if let Some(matrix) = gp.matrix {
group.matrix(matrix);
}

let mut resources = group.resources();
ctx.pop(&mut resources);
resources.finish();
}
}

ctx.initial_mask = None;
write_masks(tree, &mut writer, &mut ctx);

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

/// Convert a [`usvg` tree](Tree) into a Form XObject that can be used as part
/// of a larger document.
///
/// This method is intended for use in an existing [`PdfWriter`] workflow.
///
/// The resulting object can be used by registering a name and the `id` with a
/// page's [`/XObject`](pdf_writer::writers::Resources::x_objects) resources
/// dictionary and then invoking the [`/Do`](pdf_writer::Content::x_object)
/// operator with the name in the page's content stream.
///
/// As the conversion process may need to create multiple indirect objects in
/// the PDF, this function allocates consecutive IDs starting at `id` for its
/// objects and returns the next available ID for your future writing.
pub fn convert_tree_into(
tree: &Tree,
options: Options,
writer: &mut PdfWriter,
id: Ref,
) -> Ref {
let (c, bbox) = get_sizings(tree, &options);
let mut ctx = Context::new(&tree, &bbox, c);

ctx.next_id = id.get() + 1;

preregister(tree, writer, &mut ctx);

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

write_masks(tree, writer, &mut ctx);

let mut xobject = writer.form_xobject(id, &content);
xobject.bbox(bbox);
let mut resources = xobject.resources();
ctx.pop(&mut resources);

ctx.alloc_ref()
}

/// Calculates the bounding box and size conversions for an usvg tree.
fn get_sizings(tree: &Tree, options: &Options) -> (CoordToPdf, Rect) {
let native_size = tree.svg_node().size;
let viewport = if let Some((width, height)) = options.viewport {
(width, height)
} else {
(native_size.width(), native_size.height())
};

let c = CoordToPdf::new(
viewport,
options.dpi,
tree.svg_node().view_box,
options.aspect,
);

(
c,
Rect::new(0.0, 0.0, c.px_to_pt(viewport.0), c.px_to_pt(viewport.1)),
)
}

fn preregister(tree: &Tree, writer: &mut PdfWriter, ctx: &mut Context) {
for element in tree.defs().children() {
match *element.borrow() {
NodeKind::LinearGradient(ref lg) => {
register_functions(writer, ctx, &lg.id, &lg.base.stops);
}
NodeKind::RadialGradient(ref rg) => {
register_functions(writer, ctx, &rg.id, &rg.base.stops);
}
_ => {}
}
}
}

/// Write a content stream for a node.
fn content_stream<'a>(
node: &usvg::Node,
Expand Down
29 changes: 3 additions & 26 deletions src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use pdf_writer::types::{
TilingType,
};
use pdf_writer::writers::Shading;
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, TextStr, Writer};
use pdf_writer::{Content, Filter, Finish, Name, PdfWriter, Rect, Ref, Writer};
use usvg::{
Align, AspectRatio, FillRule, ImageKind, LineCap, LineJoin, Node, NodeExt, NodeKind,
Paint, PathSegment, Pattern, Transform, Units, ViewBox, Visibility,
Expand All @@ -23,7 +23,7 @@ use super::{
apply_clip_path, apply_mask, content_stream, form_xobject, Context, Options,
RgbaColor, SRGB,
};
use crate::convert_tree;
use crate::convert_tree_into;
use crate::defer::{PendingGS, PendingGradient};
use crate::scale::CoordToPdf;

Expand Down Expand Up @@ -681,30 +681,7 @@ impl Render for usvg::Image {
dpi: ctx.c.dpi(),
};

let bytes = convert_tree(tree, opt);
let byte_len = bytes.len();
let compressed = compress_to_vec_zlib(&bytes, 8);

let file_ref = ctx.alloc_ref();
let mut embedded = writer.embedded_file(file_ref, &compressed);
embedded.subtype(Name(b"application/pdf"));
embedded.filter(Filter::FlateDecode);
embedded.params().size(byte_len as i32);
embedded.finish();

writer
.form_xobject(image_ref, &[])
.bbox(Rect::new(
0.0,
0.0,
ctx.c.px_to_pt(rect.x() + rect.width()),
ctx.c.px_to_pt(rect.y() + rect.height()),
))
.reference()
.page_number(0)
.file()
.description(TextStr("Embedded SVG image"))
.embedded_file(file_ref);
ctx.next_id = convert_tree_into(tree, opt, writer, image_ref).get();
}

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

0 comments on commit 3cafa54

Please sign in to comment.