svg2pdf/render/
mod.rs

1use pdf_writer::{Chunk, Content, Filter, Finish, Ref};
2use usvg::{Node, Transform, Tree};
3
4use crate::util::context::Context;
5use crate::util::helper::{ContentExt, RectExt, TransformExt};
6use crate::util::resources::ResourceContainer;
7use crate::Result;
8
9pub mod clip_path;
10#[cfg(feature = "filters")]
11pub mod filter;
12pub mod gradient;
13pub mod group;
14#[cfg(feature = "image")]
15pub mod image;
16pub mod mask;
17pub mod path;
18pub mod pattern;
19#[cfg(feature = "text")]
20pub mod text;
21
22/// Write a tree into a stream. Assumes that the stream belongs to transparency group and the object
23/// that contains it has the correct bounding box set.
24pub fn tree_to_stream(
25    tree: &Tree,
26    chunk: &mut Chunk,
27    content: &mut Content,
28    ctx: &mut Context,
29    rc: &mut ResourceContainer,
30) -> Result<()> {
31    content.save_state_checked()?;
32
33    // From PDF coordinate system to SVG coordinate system
34    let initial_transform =
35        Transform::from_row(1.0, 0.0, 0.0, -1.0, 0.0, tree.size().height());
36
37    content.transform(initial_transform.to_pdf_transform());
38
39    group::render(tree.root(), chunk, content, ctx, initial_transform, None, rc)?;
40    content.restore_state();
41
42    Ok(())
43}
44
45/// Convert a tree into a XObject of size 1x1, similar to an image.
46pub fn tree_to_xobject(tree: &Tree, chunk: &mut Chunk, ctx: &mut Context) -> Result<Ref> {
47    let bbox = tree.size().to_non_zero_rect(0.0, 0.0);
48    let x_ref = ctx.alloc_ref();
49
50    let mut rc = ResourceContainer::new();
51
52    let mut content = Content::new();
53    tree_to_stream(tree, chunk, &mut content, ctx, &mut rc)?;
54    let stream = ctx.finish_content(content);
55
56    let mut x_object = chunk.form_xobject(x_ref, &stream);
57    x_object.bbox(bbox.to_pdf_rect());
58    x_object.matrix([1.0 / bbox.width(), 0.0, 0.0, 1.0 / bbox.height(), 0.0, 0.0]);
59
60    if ctx.options.compress {
61        x_object.filter(Filter::FlateDecode);
62    }
63
64    let mut resources = x_object.resources();
65    rc.finish(&mut resources);
66
67    resources.finish();
68    x_object.finish();
69
70    Ok(x_ref)
71}
72
73trait Render {
74    fn render(
75        &self,
76        chunk: &mut Chunk,
77        content: &mut Content,
78        ctx: &mut Context,
79        accumulated_transform: Transform,
80        rc: &mut ResourceContainer,
81    ) -> Result<()>;
82}
83
84impl Render for Node {
85    fn render(
86        &self,
87        chunk: &mut Chunk,
88        content: &mut Content,
89        ctx: &mut Context,
90        accumulated_transform: Transform,
91        rc: &mut ResourceContainer,
92    ) -> Result<()> {
93        match self {
94            Node::Path(ref path) => {
95                path::render(path, chunk, content, ctx, rc, accumulated_transform)
96            }
97            Node::Group(ref group) => {
98                group::render(group, chunk, content, ctx, accumulated_transform, None, rc)
99            }
100            #[cfg(feature = "image")]
101            Node::Image(ref image) => image::render(
102                image.is_visible(),
103                image.kind(),
104                None,
105                chunk,
106                content,
107                ctx,
108                rc,
109            ),
110            #[cfg(not(feature = "image"))]
111            Node::Image(_) => {
112                log::warn!("Failed convert image because the image feature was disabled. Skipping.");
113                Ok(())
114            }
115            #[cfg(feature = "text")]
116            Node::Text(ref text) => {
117                if ctx.options.embed_text {
118                    text::render(text, chunk, content, ctx, rc, accumulated_transform)
119                } else {
120                    group::render(
121                        text.flattened(),
122                        chunk,
123                        content,
124                        ctx,
125                        accumulated_transform,
126                        None,
127                        rc,
128                    )
129                }
130            }
131            #[cfg(not(feature = "text"))]
132            Node::Text(_) => {
133                log::warn!("Failed convert text because the text feature was disabled. Skipping.");
134                Ok(())
135            }
136        }
137    }
138}