dessin_pdf/
lib.rs

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					// self.content.push(printpdf::LineDashPattern {
118					// 	offset: 0,
119					// 	dash_1: Some(on as i64),
120					// 	gap_1: Some(off as i64),
121					// 	dash_2: None,
122					// 	gap_2: None,
123					// 	dash_3: None,
124					// 	gap_3: None,
125					// });
126
127					(
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}