1use dessin::{
2 export::{Export, Exporter},
3 font::FontRef,
4 prelude::*,
5};
6use nalgebra::Translation2;
7use printpdf::{
8 Color, FontId, Layer, LayerIntent, LayerInternalId, LayerSubtype, Line, LinePoint, Mm, Op,
9 PaintMode, ParsedFont, PdfDocument, PdfPage, PdfSaveOptions, Point, Polygon, PolygonRing, Px,
10 RawImage, Rgb, TextItem, TextMatrix, TextRenderingMode, WindingOrder, XObjectRotation,
11 XObjectTransform,
12};
13use std::{collections::HashMap, convert::identity, fmt};
14
15#[derive(Debug, thiserror::Error)]
16pub enum PDFError {
17 #[error("PrintPDF Image error: {0}")]
18 PrintPDFImageError(String),
19 #[error("{0}")]
20 WriteError(#[from] fmt::Error),
21 #[error("Curve has no starting point: {0:?}")]
22 CurveHasNoStartingPoint(Curve),
23 #[error("Unknown builtin font: {0}")]
24 UnknownBuiltinFont(String),
25 #[error("Orphelin layer")]
26 OrphelinLayer,
27 #[error("Can't parse font `{0} {1:?}`")]
28 CantParseFont(FontRef, FontWeight),
29 #[error("Internal error: No layer started")]
30 NoLayerStarted,
31}
32
33type PDFFontHolder = HashMap<(FontRef, FontWeight), FontId>;
34
35#[derive(Default)]
36pub struct PDFOptions {
37 pub size: Option<(f32, f32)>,
38 pub used_font: PDFFontHolder,
39}
40
41pub struct PDFExporter<'a> {
42 doc: &'a mut PdfDocument,
43 used_font: PDFFontHolder,
44 content: Vec<Op>,
45 layers: Vec<LayerInternalId>,
46}
47impl<'a> PDFExporter<'a> {
48 pub fn new_with_font(doc: &'a mut PdfDocument, used_font: PDFFontHolder) -> Self {
49 PDFExporter {
50 doc,
51 used_font,
52 content: vec![],
53 layers: vec![],
54 }
55 }
56
57 pub fn new(doc: &'a mut PdfDocument) -> Self {
58 PDFExporter::new_with_font(doc, HashMap::default())
59 }
60}
61
62impl Exporter for PDFExporter<'_> {
63 type Error = PDFError;
64
65 const CAN_EXPORT_ELLIPSE: bool = false;
66
67 fn start_style(
68 &mut self,
69 StylePosition { fill, stroke }: StylePosition,
70 ) -> Result<(), Self::Error> {
71 let layer_id = self.doc.add_layer(&Layer {
72 creator: String::new(),
73 name: String::new(),
74 intent: LayerIntent::Design,
75 usage: LayerSubtype::Artwork,
76 });
77
78 self.content.push(Op::BeginLayer { layer_id });
79
80 if let Some(fill) = fill {
81 let (r, g, b) = match fill {
82 Fill::Solid { color } => (
83 color.into_format::<f32, f32>().red,
84 color.into_format::<f32, f32>().green,
85 color.into_format::<f32, f32>().blue,
86 ),
87 };
88
89 self.content.push(Op::SetFillColor {
90 col: Color::Rgb(Rgb {
91 r,
92 g,
93 b,
94 icc_profile: None,
95 }),
96 });
97 }
98
99 if let Some(stroke) = stroke {
100 let ((r, g, b), w) = match stroke {
101 Stroke::Solid { color, width } => (
102 (
103 color.into_format::<f32, f32>().red,
104 color.into_format::<f32, f32>().green,
105 color.into_format::<f32, f32>().blue,
106 ),
107 width,
108 ),
109 Stroke::Dashed {
110 color,
111 width,
112 on: _,
113 off: _,
114 } => {
115 eprintln!("TODO: LineDashPattern");
116
117 (
128 (
129 color.into_format::<f32, f32>().red,
130 color.into_format::<f32, f32>().green,
131 color.into_format::<f32, f32>().blue,
132 ),
133 width,
134 )
135 }
136 };
137
138 self.content.extend([
139 Op::SetOutlineColor {
140 col: Color::Rgb(Rgb {
141 r,
142 g,
143 b,
144 icc_profile: None,
145 }),
146 },
147 Op::SetOutlineThickness {
148 pt: printpdf::Mm(w).into_pt(),
149 },
150 ]);
151 }
152
153 Ok(())
154 }
155
156 fn end_style(&mut self) -> Result<(), Self::Error> {
157 self.content.push(Op::EndLayer {
158 layer_id: self.layers.pop().ok_or(PDFError::NoLayerStarted)?,
159 });
160 Ok(())
161 }
162
163 fn export_image(
164 &mut self,
165 ImagePosition {
166 top_left: _,
167 top_right: _,
168 bottom_right: _,
169 bottom_left,
170 center: _,
171 width,
172 height,
173 rotation,
174 image,
175 }: ImagePosition,
176 ) -> Result<(), Self::Error> {
177 let width_px = image.width();
178 let height_px = image.height();
179
180 let dpi = 300.;
181 let raw_width = width_px as f32 * 25.4 / dpi;
182 let raw_height = height_px as f32 * 25.4 / dpi;
183
184 let scale_width = width / raw_width;
185 let scale_height = height / raw_height;
186
187 let img_id = self.doc.add_image(
188 &RawImage::decode_from_bytes(image.as_bytes(), &mut vec![])
189 .map_err(PDFError::PrintPDFImageError)?,
190 );
191
192 self.content.push(Op::UseXobject {
193 id: img_id,
194 transform: XObjectTransform {
195 translate_x: Some(Mm(bottom_left.x).into()),
196 translate_y: Some(Mm(bottom_left.y).into()),
197 rotate: Some(XObjectRotation {
198 angle_ccw_degrees: rotation.to_degrees(),
199 rotation_center_x: Px((width_px / 2) as usize),
200 rotation_center_y: Px((height_px / 2) as usize),
201 }),
202 scale_x: Some(scale_width),
203 scale_y: Some(scale_height),
204 dpi: Some(dpi),
205 },
206 });
207
208 Ok(())
209 }
210
211 fn export_curve(
212 &mut self,
213 curve: CurvePosition,
214 StylePosition { fill, stroke }: StylePosition,
215 ) -> Result<(), Self::Error> {
216 let points = curve
217 .keypoints
218 .into_iter()
219 .flat_map(|v| match v {
220 KeypointPosition::Point(p) => [
221 Some(LinePoint {
222 p: Point::new(Mm(p.x), Mm(p.y)),
223 bezier: false,
224 }),
225 None,
226 None,
227 None,
228 ]
229 .into_iter(),
230 KeypointPosition::Bezier(Bezier {
231 start,
232 start_control,
233 end_control,
234 end,
235 }) => [
236 start.map(|v| LinePoint {
237 p: Point::new(Mm(v.x), Mm(v.y)),
238 bezier: false,
239 }),
240 Some(LinePoint {
241 p: Point::new(Mm(start_control.x), Mm(start_control.y)),
242 bezier: true,
243 }),
244 Some(LinePoint {
245 p: Point::new(Mm(end_control.x), Mm(end_control.y)),
246 bezier: true,
247 }),
248 Some(LinePoint {
249 p: Point::new(Mm(end.x), Mm(end.y)),
250 bezier: true,
251 }),
252 ]
253 .into_iter(),
254 })
255 .filter_map(identity)
256 .collect::<Vec<_>>();
257
258 self.content.push(if curve.closed {
259 Op::DrawPolygon {
260 polygon: Polygon {
261 mode: match (fill, stroke) {
262 (Some(_), Some(_)) => PaintMode::FillStroke,
263 (Some(_), None) => PaintMode::Fill,
264 (None, Some(_)) => PaintMode::Stroke,
265 (None, None) => PaintMode::Clip,
266 },
267 rings: vec![PolygonRing { points }],
268 winding_order: WindingOrder::NonZero,
269 },
270 }
271 } else {
272 Op::DrawLine {
273 line: Line {
274 points,
275 is_closed: false,
276 },
277 }
278 });
279
280 Ok(())
281 }
282
283 fn export_text(
284 &mut self,
285 TextPosition {
286 text,
287 align: _,
288 font_weight,
289 on_curve: _,
290 font_size,
291 reference_start,
292 direction,
293 font,
294 }: TextPosition,
295 StylePosition { fill, stroke }: StylePosition,
296 ) -> Result<(), Self::Error> {
297 let font = font.clone().unwrap_or(FontRef::default());
298
299 let key = (font.clone(), font_weight);
300 if !self.used_font.contains_key(&key) {
301 match font::get(&font).get(font_weight) {
302 dessin::font::Font::OTF(b) | dessin::font::Font::TTF(b) => {
303 let font_id = self.doc.add_font(
304 &ParsedFont::from_bytes(&b, 0, &mut vec![])
305 .ok_or_else(|| PDFError::CantParseFont(font.clone(), font_weight))?,
306 );
307
308 self.used_font.insert(key.clone(), font_id);
309 }
310 }
311 }
312
313 let font = self.used_font[&key].clone();
314
315 let rotation = direction.y.atan2(direction.x).to_degrees();
316
317 self.content.extend([
318 Op::SetLineHeight {
319 lh: Mm(font_size).into_pt(),
320 },
321 Op::SetWordSpacing {
322 pt: Mm(font_size).into_pt(),
323 },
324 Op::SetTextRenderingMode {
325 mode: match (fill, stroke) {
326 (Some(_), Some(_)) => TextRenderingMode::FillStroke,
327 (Some(_), None) => TextRenderingMode::Fill,
328 (None, Some(_)) => TextRenderingMode::Stroke,
329 (None, None) => TextRenderingMode::Clip,
330 },
331 },
332 Op::SetTextMatrix {
333 matrix: TextMatrix::TranslateRotate(
334 Mm(reference_start.x).into_pt(),
335 Mm(reference_start.y).into_pt(),
336 rotation,
337 ),
338 },
339 Op::WriteText {
340 items: vec![TextItem::Text(text.to_string())],
341 font,
342 },
343 ]);
344
345 Ok(())
346 }
347}
348
349pub fn write_to_pdf_with_options(
350 shape: &Shape,
351 options: PDFOptions,
352 doc: &mut PdfDocument,
353) -> Result<PdfPage, PDFError> {
354 let (width, height) = options.size.unwrap_or_else(|| {
355 let bb = shape.local_bounding_box();
356 (bb.width(), bb.height())
357 });
358 let mut exporter = PDFExporter::new_with_font(doc, options.used_font);
359 let translation = Translation2::new(width / 2., height / 2.);
360 let parent_transform = nalgebra::convert(translation);
361
362 if let Shape::Style { fill, stroke, .. } = shape {
363 shape.write_into_exporter(
364 &mut exporter,
365 &parent_transform,
366 StylePosition {
367 fill: *fill,
368 stroke: *stroke,
369 },
370 )?
371 } else {
372 shape.write_into_exporter(
373 &mut exporter,
374 &parent_transform,
375 StylePosition {
376 fill: None,
377 stroke: None,
378 },
379 )?
380 }
381
382 Ok(PdfPage::new(Mm(width), Mm(height), exporter.content))
383}
384
385pub fn to_pdf_with_options(shape: &Shape, options: PDFOptions) -> Result<PdfDocument, PDFError> {
386 let mut doc = PdfDocument::new("");
387
388 _ = write_to_pdf_with_options(shape, options, &mut doc)?;
389
390 Ok(doc)
391}
392
393pub fn write_to_pdf(shape: &Shape, doc: &mut PdfDocument) -> Result<PdfPage, PDFError> {
394 write_to_pdf_with_options(shape, PDFOptions::default(), doc)
395}
396
397pub fn to_pdf(shape: &Shape) -> Result<PdfDocument, PDFError> {
398 to_pdf_with_options(shape, PDFOptions::default())
399}
400
401pub fn to_pdf_bytes(shape: &Shape) -> Result<Vec<u8>, PDFError> {
402 Ok(to_pdf(shape)?.save(
403 &PdfSaveOptions {
404 optimize: true,
405 secure: true,
406 subset_fonts: true,
407 ..Default::default()
408 },
409 &mut vec![],
410 ))
411}