krilla-rxing 0.1.1

Render barcodes (QR Codes, Aztec, Data Matrix, etc) using rxing into a krilla Surface (PDF)
Documentation
//! `krilla-rxing` extends the PDF creation library [`krilla`] with support for drawing
//! barcodes based on the [`rxing`] library. This library aims to make barcode creation
//! easy while optimising the PDF strokes s.t. the output file is as small as possible.
//!
//! # CLI
//!
//! If all you need is a tool that creates a PDF for you, take a look at [`qrcode2pdf`].
//! Contrary to the name of the crate, it comes with a tool for all barcode formats
//! supported by this library.
//!
//! To create a PDF containing a QR Code, you can run
//!
//! ```shell
//! qrcode2pdf -o barcode.pdf https://codeberg.org/msrd0/krilla-rxing/src/branch/main/cli
//! ```
//!
//! # Library
//!
//! If you want to embed barcodes into your existing PDF-writing code, there are two
//! APIs you can use. The simplest way is to call
//! [`draw_qr_code`][SurfaceExt::draw_qr_code] on krilla's [`Surface`]:
//!
//! ```rust
//! # use krilla::{geom::Point, Document};
//! use krilla_rxing::SurfaceExt as _;
//!
//! const PDF_UNITS_PER_MM: f32 = 72.0 / 25.4;
//!
//! # let mut document = Document::new();
//! # let mut page = document.start_page();
//! # let mut surface = page.surface();
//! // draw a 10 x 10 cm QR Code at the top left of the surface
//! surface
//! 	.draw_qr_code(
//! 		"https://codeberg.org/msrd0/krilla-rxing",
//! 		Point::from_xy(0.0, 0.0),
//! 		100.0 * PDF_UNITS_PER_MM
//! 	)
//! 	.expect("Failed to draw QR Code");
//! # surface.finish();
//! # page.finish();
//! # document.finish().unwrap();
//! ```
//!
//! Some other barcode are not necessarily square, and you might want to know the height
//! of the barcode. In these cases, you need to create the barcode and then draw it using
//! the [`draw_barcode`][SurfaceExt::draw_barcode] function:
//!
//! ```rust
//! # use krilla::{geom::Point, Document};
//! use krilla_rxing::{Barcode, SurfaceExt as _};
//!
//! const PDF_UNITS_PER_MM: f32 = 72.0 / 25.4;
//!
//! # let mut document = Document::new();
//! # let mut page = document.start_page();
//! # let mut surface = page.surface();
//! // draw a 10 x 10 cm PDF417 barcode at the top left of the surface
//! let barcode_width = 100.0 * PDF_UNITS_PER_MM;
//! let barcode =
//! 	Barcode::new_pdf417("https://codeberg.org/msrd0/krilla-rxing", barcode_width)
//! 		.expect("Failed to create PDF417 barcode");
//! let barcode_height = barcode.height();
//! # assert_eq!(barcode_width, barcode.width()); _ = barcode_height;
//! surface.draw_barcode(&barcode, Point::from_xy(0.0, 0.0));
//! # surface.finish();
//! # page.finish();
//! # document.finish().unwrap();
//! ```
//!
//!  [`qrcode2pdf`]: https://codeberg.org/msrd0/krilla-rxing/src/branch/main/cli

use krilla::{
	color::luma,
	geom::{PathBuilder, Point, Transform},
	num::NormalizedF32,
	paint::Fill,
	surface::Surface
};
use rxing::{
	aztec::AztecWriter, common::BitMatrix, datamatrix::DataMatrixWriter,
	pdf417::PDF417Writer, qrcode::QRCodeWriter, BarcodeFormat, Writer
};

pub type Result<T = (), E = rxing::Exceptions> = std::result::Result<T, E>;

pub struct Barcode {
	bit_matrix: BitMatrix,
	bit_size: f32,
	border: f32
}

impl Barcode {
	/// Create a new barcode from a bit matrix. The bit size is computed based on the
	/// width of the bit matrix, the preferred width and the border size.
	fn new(bit_matrix: BitMatrix, width: f32, border: f32) -> Self {
		Self {
			border,
			bit_size: width / (bit_matrix.width() as f32 + 2.0 * border),
			bit_matrix
		}
	}

	/// Create a new Aztec barcode containing `text` with width `width`.
	pub fn new_aztec(text: &str, width: f32) -> Result<Self> {
		let bit_matrix = AztecWriter.encode(text, &BarcodeFormat::AZTEC, 0, 0)?;
		Ok(Self::new(bit_matrix, width, 0.0))
	}

	/// Create a new Data Matrix barcode containing `text` with width `width`.
	pub fn new_data_matrix(text: &str, width: f32) -> Result<Self> {
		let bit_matrix =
			DataMatrixWriter.encode(text, &BarcodeFormat::DATA_MATRIX, 0, 0)?;
		Ok(Self::new(bit_matrix, width, 1.0))
	}

	/// Create a new PDF417 barcode containing `text` with width `width`.
	pub fn new_pdf417(text: &str, width: f32) -> Result<Self> {
		let bit_matrix = PDF417Writer.encode(text, &BarcodeFormat::PDF_417, 0, 0)?;
		Ok(Self::new(bit_matrix, width, 0.0))
	}

	/// Create a new QR Code containing `text` with width `width`.
	pub fn new_qr_code(text: &str, width: f32) -> Result<Self> {
		let bit_matrix = QRCodeWriter.encode(text, &BarcodeFormat::QR_CODE, 0, 0)?;
		Ok(Self::new(bit_matrix, width, 0.0))
	}

	/// Returns the width (in PDF units) that this barcode will take up. This includes
	/// any required border around the actual barcode.
	pub fn width(&self) -> f32 {
		self.bit_size * (self.bit_matrix.width() as f32 + 2.0 * self.border)
	}

	/// Returns the height (in PDF units) that this barcode will take up. This includes
	/// any required border around the actual barcode.
	pub fn height(&self) -> f32 {
		self.bit_size * (self.bit_matrix.height() as f32 + 2.0 * self.border)
	}
}

/// A set of "run-length encoded" bits. Basically just one or more bits that form can be
/// drawn together as a rectangle.
struct RleBits {
	x: u32,
	y: u32,
	w: u32,
	h: u32
}

/// "Run-length encoding" of a barcode. Drawing several bits in one path reduces PDF file
/// size.
struct Rle {
	bits: Vec<RleBits>
}

impl Rle {
	/// Create a new horizontal [`Rle`].
	fn new_horiz(bit_matrix: &BitMatrix) -> Self {
		let mut bits = Vec::new();
		let mut y = 0;
		for row in 0 .. bit_matrix.height() {
			let mut x = 0;
			let mut col = 0;
			while col < bit_matrix.width() {
				if !bit_matrix.get(col, row) {
					col += 1;
					x += 1;
					continue;
				}
				let col_start = col;
				while col < bit_matrix.width() && bit_matrix.get(col, row) {
					col += 1;
				}
				let colspan = col - col_start;
				bits.push(RleBits {
					x,
					y,
					w: colspan,
					h: 1
				});
				x += colspan;
			}
			y += 1;
		}
		Self { bits }
	}

	/// Create a new vertical [`Rle`].
	fn new_vert(bit_matrix: &BitMatrix) -> Self {
		let mut bits = Vec::new();
		let mut x = 0;
		for col in 0 .. bit_matrix.width() {
			let mut y = 0;
			let mut row = 0;
			while row < bit_matrix.height() {
				if !bit_matrix.get(col, row) {
					row += 1;
					y += 1;
					continue;
				}
				let row_start = row;
				while row < bit_matrix.height() && bit_matrix.get(col, row) {
					row += 1;
				}
				let rowspan = row - row_start;
				bits.push(RleBits {
					x,
					y,
					w: 1,
					h: rowspan
				});
				y += rowspan;
			}
			x += 1;
		}
		Self { bits }
	}

	/// The amount of rectangles need to be drawn.
	fn len(&self) -> usize {
		self.bits.len()
	}
}

impl Barcode {
	/// Draw this barcode to the surface at the specified position.
	fn draw(&self, surface: &mut Surface<'_>, x: f32, y: f32) {
		let width = self.bit_matrix.width() as f32 + 2.0 * self.border;
		let height = self.bit_matrix.height() as f32 + 2.0 * self.border;

		// We could always multiply everything with self.bit_size
		// However, that gives a lot of floating-point values that are longer than
		// simple integer values in the pdf
		// Therefore, we use these transform to save PDF file size
		surface.push_transform(&Transform::from_translate(x, y));
		surface.push_transform(&Transform::from_scale(self.bit_size, self.bit_size));

		// background: 90% white, 10% alpha
		surface.set_stroke(None);
		surface.set_fill(Some(Fill {
			paint: luma::Color::white().into(),
			opacity: NormalizedF32::new(0.9).unwrap(),
			..Default::default()
		}));
		let mut bg = PathBuilder::new();
		bg.move_to(0.0, 0.0);
		bg.line_to(width, 0.0);
		bg.line_to(width, height);
		bg.line_to(0.0, height);
		bg.close();
		let bg = bg.finish().unwrap();
		surface.draw_path(&bg);

		// barcode: 100% black
		surface.set_stroke(None);
		surface.set_fill(Some(Fill {
			paint: luma::Color::black().into(),
			..Default::default()
		}));

		// Create a more space-efficient representation of the bit matrix to save PDF
		// file size.
		let rle_horiz = Rle::new_horiz(&self.bit_matrix);
		let rle_vert = Rle::new_vert(&self.bit_matrix);
		let rle = if rle_horiz.len() > rle_vert.len() {
			rle_vert
		} else {
			rle_horiz
		};

		// Draw to the surface
		for bit in rle.bits {
			surface.push_transform(&Transform::from_translate(
				self.border + bit.x as f32,
				self.border + bit.y as f32
			));
			let mut path = PathBuilder::new();
			path.move_to(0.0, 0.0);
			path.line_to(bit.w as f32, 0.0);
			path.line_to(bit.w as f32, bit.h as f32);
			path.line_to(0.0, bit.h as f32);
			path.close();
			let path = path.finish().unwrap();
			surface.draw_path(&path);
			surface.pop();
		}

		// pop the transforms we pushed earlier
		surface.pop();
		surface.pop();
	}
}

/// Barcode writing extensions for [`krilla::surface::Surface`].
///
/// # Implementations
///
/// This interface is implemented for [`krilla::surface::Surface`] and that is the only
/// intended implementation.
///
/// The public, document functions on this interface are covered under semver. They are,
/// however, not intended for implementations outside of this crate. While those
/// implementations are technically permitted, they are _not_ covered by semver. If you
/// implement this trait yourself, pin the version of this library.
pub trait SurfaceExt {
	/// Draw an Aztec barcode containing `text` at the `top_left` position.
	fn draw_aztec(&mut self, text: &str, top_left: Point, width: f32) -> Result {
		let barcode = Barcode::new_aztec(text, width)?;
		self.draw_barcode(&barcode, top_left);
		Ok(())
	}

	/// Draw a data matrix containing `text` at the `top_left` position.
	fn draw_data_matrix(&mut self, text: &str, top_left: Point, width: f32) -> Result {
		let barcode = Barcode::new_data_matrix(text, width)?;
		self.draw_barcode(&barcode, top_left);
		Ok(())
	}

	/// Draw a PDF417 barcode containing `text` at the `top_left` position.
	fn draw_pdf417(&mut self, text: &str, top_left: Point, width: f32) -> Result {
		let barcode = Barcode::new_pdf417(text, width)?;
		self.draw_barcode(&barcode, top_left);
		Ok(())
	}

	/// Draw a QR Code containing `text` at the `top_left` position.
	fn draw_qr_code(&mut self, text: &str, top_left: Point, width: f32) -> Result {
		let barcode = Barcode::new_qr_code(text, width)?;
		self.draw_barcode(&barcode, top_left);
		Ok(())
	}

	fn draw_barcode(&mut self, barcode: &Barcode, top_left: Point);
}

impl SurfaceExt for Surface<'_> {
	fn draw_barcode(&mut self, barcode: &Barcode, top_left: Point) {
		barcode.draw(self, top_left.x, top_left.y);
	}
}