laser_pdf/elements/
svg.rs1use utils::mm_to_pt;
2
3use crate::{utils::pt_to_mm, *};
4
5pub struct Svg<'a> {
10 pub data: &'a usvg::Tree,
12}
13
14impl<'a> Element for Svg<'a> {
15 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
16 let (height, _) = calculate_size(self.data, ctx.width);
17
18 if ctx.break_appropriate_for_min_height(height) {
19 FirstLocationUsage::WillSkip
20 } else {
21 FirstLocationUsage::WillUse
22 }
23 }
24
25 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
26 let (height, element_size) = calculate_size(self.data, ctx.width);
27
28 ctx.break_if_appropriate_for_min_height(height);
29
30 element_size
31 }
32
33 fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
34 let (height, element_size) = calculate_size(self.data, ctx.width);
35
36 ctx.break_if_appropriate_for_min_height(height);
37
38 let pos = ctx.location.pos;
39
40 let (svg_chunk, svg_id) = svg2pdf::to_chunk(
41 self.data,
42 svg2pdf::ConversionOptions {
43 compress: false,
44 raster_scale: 1.5,
45 embed_text: false,
46 pdfa: true,
47 },
48 )
49 .unwrap();
50
51 let offset = ctx.pdf.alloc.get();
52 let mut max = offset;
53
54 svg_chunk.renumber_into(&mut ctx.pdf.pdf, |old| {
60 let val = offset + old.get();
61 max = max.max(val);
62 pdf_writer::Ref::new(val)
63 });
64
65 let svg_id = pdf_writer::Ref::new(offset + svg_id.get());
66 ctx.pdf.alloc = pdf_writer::Ref::new(max + 1);
67
68 let x_object = ctx.pdf.pages[ctx.location.page_idx].add_x_object(svg_id);
69
70 let layer = ctx.location.layer(ctx.pdf);
71
72 layer
73 .save_state()
74 .transform([
75 mm_to_pt(element_size.width.unwrap()),
76 0.,
77 0.,
78 mm_to_pt(element_size.height.unwrap()),
79 mm_to_pt(pos.0),
80 mm_to_pt(pos.1 - element_size.height.unwrap()),
81 ])
82 .x_object(Name(x_object.as_bytes()))
83 .restore_state();
84
85 element_size
86 }
87}
88
89#[inline]
90fn calculate_size(data: &usvg::Tree, width: WidthConstraint) -> (f32, ElementSize) {
91 let svg = data;
92 let svg_size = svg.size();
93 let svg_width = pt_to_mm(svg_size.width() as f32);
94 let svg_height = pt_to_mm(svg_size.height() as f32);
95
96 let width = width.constrain(svg_width);
97 let scale_factor = width / svg_width;
98 let height = svg_height * scale_factor;
99
100 (
101 height,
102 ElementSize {
103 width: Some(width),
104 height: Some(height),
105 },
106 )
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use insta::*;
113 use test_utils::binary_snapshots::*;
114
115 #[test]
116 fn test() {
117 const SVG: &str = "\
118 <svg
119 width=\"512\"
120 height=\"512\"
121 viewBox=\"0 0 135.46666 135.46667\"
122 version=\"1.1\"
123 id=\"svg1\"
124 xmlns=\"http://www.w3.org/2000/svg\"
125 xmlns:svg=\"http://www.w3.org/2000/svg\">
126 <defs
127 id=\"defs1\" />
128 <g
129 id=\"layer1\">
130 <rect
131 style=\"fill:#000f80;fill-opacity:1;stroke:none;stroke-width:1.3386\"
132 id=\"rect1\"
133 width=\"108.92857\"
134 height=\"72.85714\"
135 x=\"18.571426\"
136 y=\"11.428572\" />
137 <ellipse
138 style=\"fill:#008080;fill-opacity:1;stroke:none;stroke-width:2.07092\"
139 id=\"path1\"
140 cx=\"84.107147\"
141 cy=\"84.107132\"
142 rx=\"51.964283\"
143 ry=\"46.250004\" />
144 </g>
145 </svg>
146 ";
147
148 let tree = usvg::Tree::from_str(
149 SVG,
150 &usvg::Options {
151 ..Default::default()
152 },
153 )
154 .unwrap();
155
156 let bytes = test_element_bytes(TestElementParams::breakable(), |callback| {
157 callback.call(
158 &Svg { data: &tree }
159 .debug(0)
160 .show_max_width()
161 .show_last_location_max_height(),
162 );
163 });
164 assert_binary_snapshot!(".pdf", bytes);
165 }
166}