krilla_rxing/
lib.rs

1//! `krilla-rxing` extends the PDF creation library [`krilla`] with support for drawing
2//! barcodes based on the [`rxing`] library. This library aims to make barcode creation
3//! easy while optimising the PDF strokes s.t. the output file is as small as possible.
4//!
5//! # CLI
6//!
7//! If all you need is a tool that creates a PDF for you, take a look at [`qrcode2pdf`].
8//! Contrary to the name of the crate, it comes with a tool for all barcode formats
9//! supported by this library.
10//!
11//! To create a PDF containing a QR Code, you can run
12//!
13//! ```shell
14//! qrcode2pdf -o barcode.pdf https://codeberg.org/msrd0/krilla-rxing/src/branch/main/cli
15//! ```
16//!
17//! # Library
18//!
19//! If you want to embed barcodes into your existing PDF-writing code, there are two
20//! APIs you can use. The simplest way is to call
21//! [`draw_qr_code`][SurfaceExt::draw_qr_code] on krilla's [`Surface`]:
22//!
23//! ```rust
24//! # use krilla::{geom::Point, Document};
25//! use krilla_rxing::SurfaceExt as _;
26//!
27//! const PDF_UNITS_PER_MM: f32 = 72.0 / 25.4;
28//!
29//! # let mut document = Document::new();
30//! # let mut page = document.start_page();
31//! # let mut surface = page.surface();
32//! // draw a 10 x 10 cm QR Code at the top left of the surface
33//! surface
34//! 	.draw_qr_code(
35//! 		"https://codeberg.org/msrd0/krilla-rxing",
36//! 		Point::from_xy(0.0, 0.0),
37//! 		100.0 * PDF_UNITS_PER_MM
38//! 	)
39//! 	.expect("Failed to draw QR Code");
40//! # surface.finish();
41//! # page.finish();
42//! # document.finish().unwrap();
43//! ```
44//!
45//! Some other barcode are not necessarily square, and you might want to know the height
46//! of the barcode. In these cases, you need to create the barcode and then draw it using
47//! the [`draw_barcode`][SurfaceExt::draw_barcode] function:
48//!
49//! ```rust
50//! # use krilla::{geom::Point, Document};
51//! use krilla_rxing::{Barcode, SurfaceExt as _};
52//!
53//! const PDF_UNITS_PER_MM: f32 = 72.0 / 25.4;
54//!
55//! # let mut document = Document::new();
56//! # let mut page = document.start_page();
57//! # let mut surface = page.surface();
58//! // draw a 10 x 10 cm PDF417 barcode at the top left of the surface
59//! let barcode_width = 100.0 * PDF_UNITS_PER_MM;
60//! let barcode =
61//! 	Barcode::new_pdf417("https://codeberg.org/msrd0/krilla-rxing", barcode_width)
62//! 		.expect("Failed to create PDF417 barcode");
63//! let barcode_height = barcode.height();
64//! # assert_eq!(barcode_width, barcode.width()); _ = barcode_height;
65//! surface.draw_barcode(&barcode, Point::from_xy(0.0, 0.0));
66//! # surface.finish();
67//! # page.finish();
68//! # document.finish().unwrap();
69//! ```
70//!
71//!  [`qrcode2pdf`]: https://codeberg.org/msrd0/krilla-rxing/src/branch/main/cli
72
73use krilla::{
74	color::luma,
75	geom::{PathBuilder, Point, Transform},
76	num::NormalizedF32,
77	paint::Fill,
78	surface::Surface
79};
80use rxing::{
81	aztec::AztecWriter, common::BitMatrix, datamatrix::DataMatrixWriter,
82	pdf417::PDF417Writer, qrcode::QRCodeWriter, BarcodeFormat, Writer
83};
84
85pub type Result<T = (), E = rxing::Exceptions> = std::result::Result<T, E>;
86
87pub struct Barcode {
88	bit_matrix: BitMatrix,
89	bit_size: f32,
90	border: f32
91}
92
93impl Barcode {
94	/// Create a new barcode from a bit matrix. The bit size is computed based on the
95	/// width of the bit matrix, the preferred width and the border size.
96	fn new(bit_matrix: BitMatrix, width: f32, border: f32) -> Self {
97		Self {
98			border,
99			bit_size: width / (bit_matrix.width() as f32 + 2.0 * border),
100			bit_matrix
101		}
102	}
103
104	/// Create a new Aztec barcode containing `text` with width `width`.
105	pub fn new_aztec(text: &str, width: f32) -> Result<Self> {
106		let bit_matrix = AztecWriter.encode(text, &BarcodeFormat::AZTEC, 0, 0)?;
107		Ok(Self::new(bit_matrix, width, 0.0))
108	}
109
110	/// Create a new Data Matrix barcode containing `text` with width `width`.
111	pub fn new_data_matrix(text: &str, width: f32) -> Result<Self> {
112		let bit_matrix =
113			DataMatrixWriter.encode(text, &BarcodeFormat::DATA_MATRIX, 0, 0)?;
114		Ok(Self::new(bit_matrix, width, 1.0))
115	}
116
117	/// Create a new PDF417 barcode containing `text` with width `width`.
118	pub fn new_pdf417(text: &str, width: f32) -> Result<Self> {
119		let bit_matrix = PDF417Writer.encode(text, &BarcodeFormat::PDF_417, 0, 0)?;
120		Ok(Self::new(bit_matrix, width, 0.0))
121	}
122
123	/// Create a new QR Code containing `text` with width `width`.
124	pub fn new_qr_code(text: &str, width: f32) -> Result<Self> {
125		let bit_matrix = QRCodeWriter.encode(text, &BarcodeFormat::QR_CODE, 0, 0)?;
126		Ok(Self::new(bit_matrix, width, 0.0))
127	}
128
129	/// Returns the width (in PDF units) that this barcode will take up. This includes
130	/// any required border around the actual barcode.
131	pub fn width(&self) -> f32 {
132		self.bit_size * (self.bit_matrix.width() as f32 + 2.0 * self.border)
133	}
134
135	/// Returns the height (in PDF units) that this barcode will take up. This includes
136	/// any required border around the actual barcode.
137	pub fn height(&self) -> f32 {
138		self.bit_size * (self.bit_matrix.height() as f32 + 2.0 * self.border)
139	}
140}
141
142/// A set of "run-length encoded" bits. Basically just one or more bits that form can be
143/// drawn together as a rectangle.
144struct RleBits {
145	x: u32,
146	y: u32,
147	w: u32,
148	h: u32
149}
150
151/// "Run-length encoding" of a barcode. Drawing several bits in one path reduces PDF file
152/// size.
153struct Rle {
154	bits: Vec<RleBits>
155}
156
157impl Rle {
158	/// Create a new horizontal [`Rle`].
159	fn new_horiz(bit_matrix: &BitMatrix) -> Self {
160		let mut bits = Vec::new();
161		let mut y = 0;
162		for row in 0 .. bit_matrix.height() {
163			let mut x = 0;
164			let mut col = 0;
165			while col < bit_matrix.width() {
166				if !bit_matrix.get(col, row) {
167					col += 1;
168					x += 1;
169					continue;
170				}
171				let col_start = col;
172				while col < bit_matrix.width() && bit_matrix.get(col, row) {
173					col += 1;
174				}
175				let colspan = col - col_start;
176				bits.push(RleBits {
177					x,
178					y,
179					w: colspan,
180					h: 1
181				});
182				x += colspan;
183			}
184			y += 1;
185		}
186		Self { bits }
187	}
188
189	/// Create a new vertical [`Rle`].
190	fn new_vert(bit_matrix: &BitMatrix) -> Self {
191		let mut bits = Vec::new();
192		let mut x = 0;
193		for col in 0 .. bit_matrix.width() {
194			let mut y = 0;
195			let mut row = 0;
196			while row < bit_matrix.height() {
197				if !bit_matrix.get(col, row) {
198					row += 1;
199					y += 1;
200					continue;
201				}
202				let row_start = row;
203				while row < bit_matrix.height() && bit_matrix.get(col, row) {
204					row += 1;
205				}
206				let rowspan = row - row_start;
207				bits.push(RleBits {
208					x,
209					y,
210					w: 1,
211					h: rowspan
212				});
213				y += rowspan;
214			}
215			x += 1;
216		}
217		Self { bits }
218	}
219
220	/// The amount of rectangles need to be drawn.
221	fn len(&self) -> usize {
222		self.bits.len()
223	}
224}
225
226impl Barcode {
227	/// Draw this barcode to the surface at the specified position.
228	fn draw(&self, surface: &mut Surface<'_>, x: f32, y: f32) {
229		let width = self.bit_matrix.width() as f32 + 2.0 * self.border;
230		let height = self.bit_matrix.height() as f32 + 2.0 * self.border;
231
232		// We could always multiply everything with self.bit_size
233		// However, that gives a lot of floating-point values that are longer than
234		// simple integer values in the pdf
235		// Therefore, we use these transform to save PDF file size
236		surface.push_transform(&Transform::from_translate(x, y));
237		surface.push_transform(&Transform::from_scale(self.bit_size, self.bit_size));
238
239		// background: 90% white, 10% alpha
240		surface.set_stroke(None);
241		surface.set_fill(Some(Fill {
242			paint: luma::Color::white().into(),
243			opacity: NormalizedF32::new(0.9).unwrap(),
244			..Default::default()
245		}));
246		let mut bg = PathBuilder::new();
247		bg.move_to(0.0, 0.0);
248		bg.line_to(width, 0.0);
249		bg.line_to(width, height);
250		bg.line_to(0.0, height);
251		bg.close();
252		let bg = bg.finish().unwrap();
253		surface.draw_path(&bg);
254
255		// barcode: 100% black
256		surface.set_stroke(None);
257		surface.set_fill(Some(Fill {
258			paint: luma::Color::black().into(),
259			..Default::default()
260		}));
261
262		// Create a more space-efficient representation of the bit matrix to save PDF
263		// file size.
264		let rle_horiz = Rle::new_horiz(&self.bit_matrix);
265		let rle_vert = Rle::new_vert(&self.bit_matrix);
266		let rle = if rle_horiz.len() > rle_vert.len() {
267			rle_vert
268		} else {
269			rle_horiz
270		};
271
272		// Draw to the surface
273		for bit in rle.bits {
274			surface.push_transform(&Transform::from_translate(
275				self.border + bit.x as f32,
276				self.border + bit.y as f32
277			));
278			let mut path = PathBuilder::new();
279			path.move_to(0.0, 0.0);
280			path.line_to(bit.w as f32, 0.0);
281			path.line_to(bit.w as f32, bit.h as f32);
282			path.line_to(0.0, bit.h as f32);
283			path.close();
284			let path = path.finish().unwrap();
285			surface.draw_path(&path);
286			surface.pop();
287		}
288
289		// pop the transforms we pushed earlier
290		surface.pop();
291		surface.pop();
292	}
293}
294
295/// Barcode writing extensions for [`krilla::surface::Surface`].
296///
297/// # Implementations
298///
299/// This interface is implemented for [`krilla::surface::Surface`] and that is the only
300/// intended implementation.
301///
302/// The public, document functions on this interface are covered under semver. They are,
303/// however, not intended for implementations outside of this crate. While those
304/// implementations are technically permitted, they are _not_ covered by semver. If you
305/// implement this trait yourself, pin the version of this library.
306pub trait SurfaceExt {
307	/// Draw an Aztec barcode containing `text` at the `top_left` position.
308	fn draw_aztec(&mut self, text: &str, top_left: Point, width: f32) -> Result {
309		let barcode = Barcode::new_aztec(text, width)?;
310		self.draw_barcode(&barcode, top_left);
311		Ok(())
312	}
313
314	/// Draw a data matrix containing `text` at the `top_left` position.
315	fn draw_data_matrix(&mut self, text: &str, top_left: Point, width: f32) -> Result {
316		let barcode = Barcode::new_data_matrix(text, width)?;
317		self.draw_barcode(&barcode, top_left);
318		Ok(())
319	}
320
321	/// Draw a PDF417 barcode containing `text` at the `top_left` position.
322	fn draw_pdf417(&mut self, text: &str, top_left: Point, width: f32) -> Result {
323		let barcode = Barcode::new_pdf417(text, width)?;
324		self.draw_barcode(&barcode, top_left);
325		Ok(())
326	}
327
328	/// Draw a QR Code containing `text` at the `top_left` position.
329	fn draw_qr_code(&mut self, text: &str, top_left: Point, width: f32) -> Result {
330		let barcode = Barcode::new_qr_code(text, width)?;
331		self.draw_barcode(&barcode, top_left);
332		Ok(())
333	}
334
335	fn draw_barcode(&mut self, barcode: &Barcode, top_left: Point);
336}
337
338impl SurfaceExt for Surface<'_> {
339	fn draw_barcode(&mut self, barcode: &Barcode, top_left: Point) {
340		barcode.draw(self, top_left.x, top_left.y);
341	}
342}