Skip to content

Commit

Permalink
Make PNG file output fallible
Browse files Browse the repository at this point in the history
  • Loading branch information
staticintlucas committed Jun 7, 2024
1 parent 3ddf660 commit 09546f4
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 15 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ key = { package = "keyset-key", path = "keyset-key", version = "0.3.2" }
profile = { package = "keyset-profile", path = "keyset-profile", version = "0.3.2" }

assert_matches = "1.5"
array-util = "1.0"
euclid = "0.22"
indoc = "2.0"
interp = { version = "1.0", features = ["interp_array"] }
Expand Down
1 change: 1 addition & 0 deletions keyset-drawing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ geom.workspace = true
key.workspace = true
profile.workspace = true

array-util.workspace = true
log.workspace = true
miniz_oxide = { workspace = true, optional = true }
pdf-writer = { workspace = true, optional = true }
Expand Down
50 changes: 50 additions & 0 deletions keyset-drawing/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use std::fmt;

use geom::Size;

use crate::png::Pixel;

/// A drawing creation error
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum Error {
/// The drawing is larger than the maximum PNG dimensions
#[cfg(feature = "svg")]
PngDimensionsError(Size<Pixel>),
}

impl fmt::Display for Error {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
#[cfg(feature = "svg")]
Self::PngDimensionsError(dims) => write!(f, "invalid PNG dimensions {dims:?}"),
}
}
}

impl std::error::Error for Error {}

#[cfg(test)]
mod tests {
use geom::Point;

use crate::Options;

#[test]
fn error_fmt() {
let key1 = key::Key::example();
let key2 = {
let mut tmp = key1.clone();
tmp.position = Point::new(1e20, 1e20);
tmp
};

let error = Options::default()
.draw(&[key1, key2])
.to_png(1.0)
.unwrap_err();

assert!(format!("{error}").starts_with("invalid PNG dimensions "));
}
}
11 changes: 9 additions & 2 deletions keyset-drawing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
//!
//! [keyset]: https://crates.io/crates/keyset
mod error;
mod imp;
#[cfg(feature = "pdf")]
mod pdf;
Expand All @@ -18,6 +19,8 @@ use geom::{Dot, Length, Point, Rect, Size, Unit, DOT_PER_UNIT};
use key::Key;
use profile::Profile;

pub use error::Error;

pub(crate) use imp::{KeyDrawing, KeyPath};

/// A drawing
Expand Down Expand Up @@ -61,10 +64,14 @@ impl Drawing {
}

/// Encode the drawing as a PNG
///
/// # Errors
///
/// Returns [`Error::PngDimensionsError`] if the drawing is too large or too small to be
/// encoded as a PNG.
#[cfg(feature = "png")]
#[inline]
#[must_use]
pub fn to_png(&self, ppi: f32) -> Vec<u8> {
pub fn to_png(&self, ppi: f32) -> Result<Vec<u8>, Error> {
png::draw(self, geom::Scale::new(ppi))
}

Expand Down
22 changes: 10 additions & 12 deletions keyset-drawing/src/png.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
use array_util::ArrayExt;
use geom::{
Dot, Inch, PathSegment, Point, Scale, ToTransform, Transform, DOT_PER_INCH, DOT_PER_UNIT,
};
use saturate::SaturatingFrom;
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Shader, Stroke, Transform as SkiaTransform};

use crate::{Drawing, KeyDrawing, KeyPath};
use crate::{Drawing, Error, KeyDrawing, KeyPath};

#[derive(Debug, Clone, Copy)]
pub struct Pixel;

pub fn draw(drawing: &Drawing, ppi: Scale<Inch, Pixel>) -> Vec<u8> {
pub fn draw(drawing: &Drawing, ppi: Scale<Inch, Pixel>) -> Result<Vec<u8>, Error> {
let scale = (DOT_PER_INCH.inverse() * ppi) * Scale::<Pixel, Pixel>::new(drawing.scale);
let size = drawing.bounds.size() * DOT_PER_UNIT * scale;

// TODO don't clamp and just return Error?
let [width, height] = size
let mut pixmap = size
.to_array()
.map(|dim| u32::saturating_from(dim.ceil()).clamp(1, u32::saturating_from(i32::MAX) / 4));

let mut pixmap = Pixmap::new(width, height)
.unwrap_or_else(|| unreachable!("width/height are within range here"));
.try_map_ext(|dim| usize::saturating_from(dim.ceil()).try_into().ok())
.and_then(|[width, height]| Pixmap::new(width, height))
.ok_or(Error::PngDimensionsError(size))?;

pixmap.fill(tiny_skia::Color::TRANSPARENT);

Expand All @@ -28,10 +27,9 @@ pub fn draw(drawing: &Drawing, ppi: Scale<Inch, Pixel>) -> Vec<u8> {
draw_key(&mut pixmap, key, transform);
}

// TODO return error on failure
pixmap
Ok(pixmap
.encode_png()
.unwrap_or_else(|_| unreachable!("writing to Vec<_> should not fail"))
.unwrap_or_else(|_| unreachable!("writing to Vec<_> should not fail")))
}

fn draw_key(pixmap: &mut Pixmap, key: &KeyDrawing, transform: Transform<Dot, Pixel>) {
Expand Down Expand Up @@ -139,7 +137,7 @@ mod tests {
let keys = [Key::example()];
let drawing = options.draw(&keys);

let png = drawing.to_png(96.0);
let png = drawing.to_png(96.0).unwrap();

let pixmap = Pixmap::decode_png(&png).unwrap();
assert_eq!(pixmap.width(), 72);
Expand Down
2 changes: 1 addition & 1 deletion keyset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
//! // Save output
//! let path = std::env::current_dir()?;
//! std::fs::write(path.join("output.svg"), drawing.to_svg())?;
//! std::fs::write(path.join("output.png"), drawing.to_png(96.0))?;
//! std::fs::write(path.join("output.png"), drawing.to_png(96.0)?)?;
//! std::fs::write(path.join("output.pdf"), drawing.to_pdf())?;
//!
//! # Ok(())
Expand Down

0 comments on commit 09546f4

Please sign in to comment.