Skip to main content

bwipp/
render.rs

1//! Renderers — turn an [`Encoded`] barcode into bytes (SVG or PNG).
2
3use crate::encoding::{BitMatrix, Encoded, LinearPattern, Postal4Pattern, StackedPattern};
4use crate::error::Error;
5use crate::options::Options;
6use crate::symbology::Symbology;
7
8mod png;
9mod svg;
10
11/// Output formats.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Format {
14    /// Scalable vector graphics (string).
15    Svg,
16    /// PNG raster image.
17    Png,
18}
19
20/// Render directly to an SVG string.
21///
22/// # Errors
23/// Returns [`Error::InvalidData`] if `data` doesn't match the symbology's
24/// input format, or [`Error::InvalidOption`] if `opts.extras` carries a
25/// symbology-specific switch the encoder doesn't recognise.
26///
27/// # Example
28/// ```
29/// use bwipp::{render_svg, Options, Symbology};
30/// let svg = render_svg(Symbology::Code39, "HELLO", &Options::default()).unwrap();
31/// assert!(svg.starts_with("<svg"));
32/// ```
33pub fn render_svg(symbology: Symbology, data: &str, opts: &Options) -> Result<String, Error> {
34    let encoded = symbology.encode(data, opts)?;
35    Ok(match encoded {
36        Encoded::Linear(p) => svg::render_linear(&p, opts),
37        Encoded::Matrix(m) => svg::render_matrix(&m, opts),
38        Encoded::Postal4State(p) => svg::render_postal4(&p, opts),
39        Encoded::Stacked(p) => svg::render_stacked(&p, opts),
40        Encoded::Dots(d) => svg::render_dots(&d, opts),
41        Encoded::Hex(s) => svg::render_hex(&s, opts),
42        Encoded::ColorMatrix(m) => svg::render_color_matrix(&m, opts),
43    })
44}
45
46/// Render to PNG bytes.
47///
48/// # Errors
49/// Same as [`render_svg`]. Also returns [`Error::Backend`] if the PNG
50/// encoder library reports a write failure (shouldn't happen with the
51/// default `Vec<u8>` writer).
52///
53/// # Example
54/// ```
55/// use bwipp::{render_png, Options, Symbology};
56/// let png = render_png(Symbology::Code39, "HELLO", &Options::default()).unwrap();
57/// assert_eq!(&png[..4], b"\x89PNG");
58/// ```
59pub fn render_png(symbology: Symbology, data: &str, opts: &Options) -> Result<Vec<u8>, Error> {
60    let encoded = symbology.encode(data, opts)?;
61    match encoded {
62        Encoded::Linear(p) => png::render_linear(&p, opts),
63        Encoded::Matrix(m) => png::render_matrix(&m, opts),
64        Encoded::Postal4State(p) => png::render_postal4(&p, opts),
65        Encoded::Stacked(p) => png::render_stacked(&p, opts),
66        Encoded::Dots(d) => png::render_dots(&d, opts),
67        Encoded::Hex(s) => png::render_hex(&s, opts),
68        Encoded::ColorMatrix(m) => png::render_color_matrix(&m, opts),
69    }
70}
71
72#[allow(dead_code)]
73pub(crate) fn iter_linear_runs(p: &LinearPattern) -> impl Iterator<Item = (u32, bool)> + '_ {
74    p.bars
75        .iter()
76        .enumerate()
77        .map(|(i, &width)| (u32::from(width), i % 2 == 0))
78}
79
80#[allow(dead_code)]
81pub(crate) fn matrix_dimensions(m: &BitMatrix) -> (usize, usize) {
82    (m.width(), m.height())
83}
84
85#[allow(dead_code)]
86pub(crate) fn postal4_bar_count(p: &Postal4Pattern) -> usize {
87    p.len()
88}
89
90#[allow(dead_code)]
91pub(crate) fn stacked_row_count(p: &StackedPattern) -> usize {
92    p.height_rows()
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    /// Stage 11.A8c — pin the helper accessors at the bottom of
100    /// `render.rs` (`iter_linear_runs`, `matrix_dimensions`,
101    /// `postal4_bar_count`, `stacked_row_count`). They have no
102    /// production callers (gated by `#[allow(dead_code)]`), but they
103    /// remain in the API as utilities for downstream tooling /
104    /// future renderers. Mutations on the body (e.g. swap width and
105    /// height, drop the `i % 2 == 0` bar-polarity flip,
106    /// `p.height_rows()` → `0`) would silently pass — there's nothing
107    /// exercising them.
108    #[test]
109    fn helper_accessors_return_expected_values() {
110        use crate::encoding::{Bar4State, LinearPattern, Postal4Pattern};
111
112        // iter_linear_runs: yields (width, is_bar) for each entry,
113        // alternating bar→space→bar starting with bar.
114        let lp = LinearPattern {
115            bars: vec![1, 2, 3, 4],
116            text: None,
117        };
118        let runs: Vec<(u32, bool)> = iter_linear_runs(&lp).collect();
119        assert_eq!(
120            runs,
121            vec![(1, true), (2, false), (3, true), (4, false)],
122            "bar/space alternation must start with bar (i==0)"
123        );
124
125        // matrix_dimensions: (width, height) tuple straight off BitMatrix.
126        let m = BitMatrix::new(7, 4);
127        assert_eq!(matrix_dimensions(&m), (7, 4));
128
129        // postal4_bar_count: returns the underlying len().
130        let p = Postal4Pattern {
131            bars: vec![Bar4State::Full; 5],
132            text: None,
133        };
134        assert_eq!(postal4_bar_count(&p), 5);
135
136        // stacked_row_count: returns underlying height_rows().
137        let sp = StackedPattern::new(
138            vec![
139                LinearPattern {
140                    bars: vec![1, 2, 3],
141                    text: None,
142                },
143                LinearPattern {
144                    bars: vec![3, 2, 1],
145                    text: None,
146                },
147            ],
148            None,
149        )
150        .unwrap();
151        assert_eq!(stacked_row_count(&sp), 2);
152    }
153}