Skip to content

Commit

Permalink
release: 0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
joshstoik1 committed Jan 9, 2025
2 parents 9c64e14 + c431139 commit 2b5a246
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 42 deletions.
36 changes: 18 additions & 18 deletions CREDITS.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
# Project Dependencies
Package: pxsum
Version: 0.2.0
Version: 0.3.0
Target: x86_64-unknown-linux-gnu
Generated: 2024-12-11 21:14:29 UTC
Generated: 2025-01-09 20:16:10 UTC

| Package | Version | Author(s) | License |
| ---- | ---- | ---- | ---- |
| [adler2](https://github.com/oyvindln/adler2) | 2.0.0 | [Jonas Schievink](mailto:[email protected]) and [oyvindln](mailto:[email protected]) | 0BSD OR MIT OR Apache-2.0 |
| [anyhow](https://github.com/dtolnay/anyhow) | 1.0.94 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [anyhow](https://github.com/dtolnay/anyhow) | 1.0.95 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [**argyle**](https://github.com/Blobfolio/argyle) | 0.10.1 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [arrayref](https://github.com/droundy/arrayref) | 0.3.9 | [David Roundy](mailto:[email protected]) | BSD-2-Clause |
| [arrayvec](https://github.com/bluss/arrayvec) | 0.7.6 | bluss | MIT OR Apache-2.0 |
| [autocfg](https://github.com/cuviper/autocfg) ⚒️ | 1.4.0 | [Josh Stone](mailto:[email protected]) | Apache-2.0 OR MIT |
| [bitflags](https://github.com/bitflags/bitflags) | 1.3.2 | The Rust Project Developers | MIT OR Apache-2.0 |
| [**blake3**](https://github.com/BLAKE3-team/BLAKE3) | 1.5.5 | [Jack O'Connor](mailto:[email protected]) and Samuel Neves | CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception |
| [bytemuck](https://github.com/Lokathor/bytemuck) | 1.20.0 | [Lokathor](mailto:[email protected]) | Zlib OR Apache-2.0 OR MIT |
| [bytemuck](https://github.com/Lokathor/bytemuck) | 1.21.0 | [Lokathor](mailto:[email protected]) | Zlib OR Apache-2.0 OR MIT |
| [byteorder](https://github.com/BurntSushi/byteorder) | 1.5.0 | [Andrew Gallant](mailto:[email protected]) | Unlicense OR MIT |
| [byteorder-lite](https://github.com/image-rs/byteorder-lite) | 0.1.0 | | Unlicense OR MIT |
| [cc](https://github.com/rust-lang/cc-rs) ⚒️ | 1.2.3 | [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [cc](https://github.com/rust-lang/cc-rs) ⚒️ | 1.2.7 | [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [cfg-if](https://github.com/alexcrichton/cfg-if) | 1.0.0 | [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [cmake](https://github.com/rust-lang/cmake-rs) ⚒️ | 0.1.52 | [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [color_quant](https://github.com/image-rs/color_quant.git) | 1.1.0 | [nwin](mailto:[email protected]) | MIT |
| [constant_time_eq](https://github.com/cesarb/constant_time_eq) | 0.3.1 | [Cesar Eduardo Barros](mailto:[email protected]) | CC0-1.0 OR MIT-0 OR Apache-2.0 |
| [crc32fast](https://github.com/srijs/rust-crc32fast) | 1.4.2 | [Sam Rijs](mailto:[email protected]) and [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [**crossbeam-channel**](https://github.com/crossbeam-rs/crossbeam) | 0.5.13 | | MIT OR Apache-2.0 |
| [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) | 0.8.20 | | MIT OR Apache-2.0 |
| [**dactyl**](https://github.com/Blobfolio/dactyl) | 0.9.0 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [**crossbeam-channel**](https://github.com/crossbeam-rs/crossbeam) | 0.5.14 | | MIT OR Apache-2.0 |
| [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) | 0.8.21 | | MIT OR Apache-2.0 |
| [**dactyl**](https://github.com/Blobfolio/dactyl) | 0.9.1 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [darling](https://github.com/TedDriggs/darling) | 0.20.10 | [Ted Driggs](mailto:[email protected]) | MIT |
| [darling_core](https://github.com/TedDriggs/darling) | 0.20.10 | [Ted Driggs](mailto:[email protected]) | MIT |
| [darling_macro](https://github.com/TedDriggs/darling) | 0.20.10 | [Ted Driggs](mailto:[email protected]) | MIT |
Expand All @@ -36,7 +36,7 @@
| [fdeflate](https://github.com/image-rs/fdeflate) | 0.3.7 | The image-rs Developers | MIT OR Apache-2.0 |
| [flate2](https://github.com/rust-lang/flate2-rs) | 1.0.35 | [Alex Crichton](mailto:[email protected]) and [Josh Triplett](mailto:[email protected]) | MIT OR Apache-2.0 |
| [fnv](https://github.com/servo/rust-fnv) | 1.0.7 | [Alex Crichton](mailto:[email protected]) | Apache-2.0 OR MIT |
| [**fyi_msg**](https://github.com/Blobfolio/fyi) | 1.4.0 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [**fyi_msg**](https://github.com/Blobfolio/fyi) | 1.5.0 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [gif](https://github.com/image-rs/image-gif) | 0.13.1 | The image-rs Developers | MIT OR Apache-2.0 |
| [half](https://github.com/starkat99/half-rs) | 2.4.1 | [Kathryn Long](mailto:[email protected]) | MIT OR Apache-2.0 |
| [hash32](https://github.com/japaric/hash32) | 0.3.1 | [Jorge Aparicio](mailto:[email protected]) | MIT OR Apache-2.0 |
Expand All @@ -51,34 +51,34 @@
| [jpegxl-sys](https://github.com/inflation/jpegxl-rs) | 0.11.2+libjxl-0.11.1 | [Inflation](mailto:[email protected]) | GPL-3.0-or-later |
| [**libavif**](https://github.com/njaard/libavif-rs) | 0.14.0 | [Charles Samuels](mailto:[email protected]) and [Paolo Barbolini](mailto:[email protected]) | BSD-2-Clause |
| [libavif-sys](https://github.com/njaard/libavif-rs) | 0.17.0+libavif.1.0.4 | [Charles Samuels](mailto:[email protected]) and [Paolo Barbolini](mailto:[email protected]) | BSD-2-Clause |
| [libc](https://github.com/rust-lang/libc) | 0.2.168 | The Rust Project Developers | MIT OR Apache-2.0 |
| [libc](https://github.com/rust-lang/libc) | 0.2.169 | The Rust Project Developers | MIT OR Apache-2.0 |
| [libdav1d-sys](https://github.com/njaard/libavif-rs) | 0.7.1+libdav1d.1.4.3 | [Charles Samuels](mailto:[email protected]), [Paolo Barbolini](mailto:[email protected]), and [Kornel](mailto:[email protected]) | BSD-2-Clause |
| [log](https://github.com/rust-lang/log) | 0.4.22 | The Rust Project Developers | MIT OR Apache-2.0 |
| [miniz_oxide](https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide) | 0.8.0 | [Frommi](mailto:[email protected]) and [oyvindln](mailto:[email protected]) | MIT OR Zlib OR Apache-2.0 |
| [miniz_oxide](https://github.com/Frommi/miniz_oxide/tree/master/miniz_oxide) | 0.8.2 | [Frommi](mailto:[email protected]) and [oyvindln](mailto:[email protected]) | MIT OR Zlib OR Apache-2.0 |
| [num-traits](https://github.com/rust-num/num-traits) | 0.2.19 | The Rust Project Developers | MIT OR Apache-2.0 |
| [openjpeg-sys](https://github.com/kornelski/openjpeg-sys) | 1.0.11 | [Kornel](mailto:[email protected]) | BSD-2-Clause |
| [pkg-config](https://github.com/rust-lang/pkg-config-rs) ⚒️ | 0.3.31 | [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [png](https://github.com/image-rs/image-png) | 0.17.15 | The image-rs Developers | MIT OR Apache-2.0 |
| [png](https://github.com/image-rs/image-png) | 0.17.16 | The image-rs Developers | MIT OR Apache-2.0 |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | 1.0.92 | [David Tolnay](mailto:[email protected]) and [Alex Crichton](mailto:[email protected]) | MIT OR Apache-2.0 |
| [quick-error](http://github.com/tailhook/quick-error) | 2.0.1 | [Paul Colomiets](mailto:[email protected]) and [Colin Kiegel](mailto:[email protected]) | MIT OR Apache-2.0 |
| [quote](https://github.com/dtolnay/quote) | 1.0.37 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [quote](https://github.com/dtolnay/quote) | 1.0.38 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [same-file](https://github.com/BurntSushi/same-file) | 1.0.6 | [Andrew Gallant](mailto:[email protected]) | Unlicense OR MIT |
| [shlex](https://github.com/comex/rust-shlex) ⚒️ | 1.3.0 | [comex](mailto:[email protected]), [Fenhl](mailto:[email protected]), [Adrian Taylor](mailto:[email protected]), [Alex Touchet](mailto:[email protected]), [Daniel Parks](mailto:dp+[email protected]), and [Garrett Berg](mailto:[email protected]) | MIT OR Apache-2.0 |
| [simd-adler32](https://github.com/mcountryman/simd-adler32) | 0.3.7 | [Marvin Countryman](mailto:[email protected]) | MIT |
| [stable_deref_trait](https://github.com/storyyeller/stable_deref_trait) | 1.2.0 | [Robert Grosse](mailto:[email protected]) | MIT OR Apache-2.0 |
| [strsim](https://github.com/rapidfuzz/strsim-rs) | 0.11.1 | [Danny Guo](mailto:[email protected]) and [maxbachmann](mailto:[email protected]) | MIT |
| [syn](https://github.com/dtolnay/syn) | 2.0.90 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [syn](https://github.com/dtolnay/syn) | 2.0.95 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [thiserror](https://github.com/dtolnay/thiserror) | 1.0.69 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [thiserror](https://github.com/dtolnay/thiserror) | 2.0.6 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [thiserror](https://github.com/dtolnay/thiserror) | 2.0.10 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [thiserror-impl](https://github.com/dtolnay/thiserror) | 1.0.69 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [thiserror-impl](https://github.com/dtolnay/thiserror) | 2.0.6 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [thiserror-impl](https://github.com/dtolnay/thiserror) | 2.0.10 | [David Tolnay](mailto:[email protected]) | MIT OR Apache-2.0 |
| [tiff](https://github.com/image-rs/image-tiff) | 0.9.1 | The image-rs Developers | MIT |
| [**trimothy**](https://github.com/Blobfolio/trimothy) | 0.6.0 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [**trimothy**](https://github.com/Blobfolio/trimothy) | 0.6.1 | [Josh Stoik](mailto:[email protected]) | WTFPL |
| [unicode-ident](https://github.com/dtolnay/unicode-ident) | 1.0.14 | [David Tolnay](mailto:[email protected]) | (MIT OR Apache-2.0) AND Unicode-3.0 |
| [**walkdir**](https://github.com/BurntSushi/walkdir) | 2.5.0 | [Andrew Gallant](mailto:[email protected]) | Unlicense OR MIT |
| [weezl](https://github.com/image-rs/lzw) | 0.1.8 | The image-rs Developers | MIT OR Apache-2.0 |
| zune-core | 0.4.12 | | MIT OR Apache-2.0 OR Zlib |
| [zune-jpeg](https://github.com/etemesi254/zune-image/tree/dev/crates/zune-jpeg) | 0.4.13 | [caleb](mailto:[email protected]) | MIT OR Apache-2.0 OR Zlib |
| [zune-jpeg](https://github.com/etemesi254/zune-image/tree/dev/crates/zune-jpeg) | 0.4.14 | [caleb](mailto:[email protected]) | MIT OR Apache-2.0 OR Zlib |

### Legend

Expand Down
10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pxsum"
version = "0.2.0"
version = "0.3.0"
license = "WTFPL"
authors = ["Josh Stoik <[email protected]>"]
edition = "2021"
Expand All @@ -10,7 +10,7 @@ publish = false

[package.metadata.deb]
maintainer = "Josh Stoik <[email protected]>"
copyright = "2024, Blobfolio, LLC <[email protected]>"
copyright = "2025, Blobfolio, LLC <[email protected]>"
license-file = ["./LICENSE", "0"]
extended-description = ""
revision = "1"
Expand Down Expand Up @@ -83,15 +83,15 @@ description = "Limit parallelization to this many threads (instead of giving eac
path = false

[[package.metadata.bashman.arguments]]
label = "<PATH(s)…>"
description = "One or more image and/or directory paths."
label = "<FILE(s)…>"
description = "One or more image file paths to checksum, or if -c/--check, one or more text file paths containing pxsums to verify. With no FILE(S) or -, input is read from STDIN."

[dependencies]
argyle = "0.10.*"
blake3 = "=1.5.5"
crossbeam-channel = "=0.5.*"
dactyl = "0.9.*"
fyi_msg = "1.4.*"
fyi_msg = "1.5.*"
trimothy = "0.6.*"
walkdir = "=2.5.*"

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Evidently WebP took some liberties with the negative space…

## Supported Image Formats

Pxsum can detect and decode (most) images in the following formats:
Pxsum can detect and decode (most) single-frame images in the following formats:

* AVIF
* BMP
Expand All @@ -50,6 +50,8 @@ Pxsum can detect and decode (most) images in the following formats:
* TIFF
* WebP

Animated images are not supported.

(If you'd like to see support for something else, just open an [issue](https://github.com/Blobfolio/pxsum/issues).)

Image file paths passed to pxsum must end with extensions associated with these types or they will be silently ignored.
Expand Down
10 changes: 5 additions & 5 deletions release/man/pxsum.1
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
.TH "PXSUM" "1" "December 2024" "pxsum v0.2.0" "User Commands"
.TH "PXSUM" "1" "January 2025" "pxsum v0.3.0" "User Commands"
.SH NAME
PXSUM \- Manual page for pxsum v0.2.0.
PXSUM \- Manual page for pxsum v0.3.0.
.SH DESCRIPTION
Calculate and verify checksums for decoded image pixel data.
.SS USAGE:
.TP
pxsum [FLAGS] [OPTIONS] <PATH(s)…>
pxsum [FLAGS] [OPTIONS] <FILE(s)…>
.SS FLAGS:
.TP
\fB\-\-bench\fR
Expand Down Expand Up @@ -43,5 +43,5 @@ Recursively search <DIR> for image files and pxsum them (along with any other FI
Limit parallelization to this many threads (instead of giving each logical core its own image to work on). If negative, the value will be subtracted from the total number of logical cores.
.SS TRAILING:
.TP
\fB<PATH(s)…>\fR
One or more image and/or directory paths.
\fB<FILE(s)…>\fR
One or more image file paths to checksum, or if \-c/\-\-check, one or more text file paths containing pxsums to verify. With no FILE(S) or \-, input is read from STDIN.
Binary file added skel/assets/flyfuk.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added skel/assets/flyfuk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added skel/assets/flyfuk.webp
Binary file not shown.
4 changes: 4 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ EXIT CODES:
/// error, a warning of some sort (that may or may not be used), or an abort
/// hint for "special" screens like Help and Version.
pub(super) enum PxsumError {
/// # Animation.
Animation,

/// # Image decode failed.
Decode,

Expand Down Expand Up @@ -147,6 +150,7 @@ impl fmt::Display for PxsumError {
"{n} computed checksum{} did NOT match",
if n.get() ==1 { "" } else { "s" }
),
Self::Animation => "Image contains animation.",
Self::Decode => "Decoding failed.",
Self::Dimensions => "Invalid image dimensions.",
Self::JobServer => "Job server choked!",
Expand Down
86 changes: 74 additions & 12 deletions src/img.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ use crate::{
use image::{
DynamicImage,
ImageFormat,
ImageReader,
};
use std::num::{
NonZeroU32,
Wrapping,
use std::{
io::Cursor,
num::{
NonZeroU32,
NonZeroUsize,
Wrapping,
},
};



/// # RGBA Pixel Size.
///
/// Each channel is one byte, four in total.
const RGBA_SIZE: NonZeroUsize = NonZeroUsize::new(4).unwrap();



#[derive(Debug, Clone, Copy, Eq, PartialEq)]
/// # Image Kind.
///
Expand Down Expand Up @@ -75,7 +87,7 @@ impl TryFrom<ImageFormat> for PxKind {
impl PxKind {
/// # Decode.
fn decode(self, src: &[u8]) -> Result<DynamicImage, PxsumError> {
use jpegxl_rs::image::ToDynamic;
use image::{AnimationDecoder, codecs};

#[cold]
/// # Decode AVIF.
Expand Down Expand Up @@ -107,6 +119,7 @@ impl PxKind {
///
/// Not a popular format, hence cold.
fn decode_jpegxl(src: &[u8]) -> Result<DynamicImage, PxsumError> {
use jpegxl_rs::image::ToDynamic;
jpegxl_rs::decoder_builder()
.build()
.and_then(|dec| dec.decode_to_image(src))
Expand All @@ -118,12 +131,28 @@ impl PxKind {
// Most decoding is handled by the image crate.
let fmt = match self {
Self::Bmp => ImageFormat::Bmp,
Self::Gif => ImageFormat::Gif,
Self::Ico => ImageFormat::Ico,
Self::Jpeg => ImageFormat::Jpeg,
Self::Png => ImageFormat::Png,
Self::Tiff => ImageFormat::Tiff,
Self::WebP => ImageFormat::WebP,

// Check for animation.
Self::Gif =>
// This decoder lacks a built-in method for animation-checking
// so we have to decode and count the frames instead. Yuck!
if codecs::gif::GifDecoder::new(Cursor::new(src))?.into_frames().take(2).count() == 1 {
ImageFormat::Gif
}
else { return Err(PxsumError::Animation); },
Self::Png =>
if codecs::png::PngDecoder::new(Cursor::new(src)).and_then(|p| p.is_apng())? {
return Err(PxsumError::Animation);
}
else { ImageFormat::Png },
Self::WebP =>
if codecs::webp::WebPDecoder::new(Cursor::new(src))?.has_animation() {
return Err(PxsumError::Animation);
}
else { ImageFormat::WebP },

// The image crate doesn't _really_ support AVIF yet, so we need to
// step in for these.
Expand All @@ -136,7 +165,10 @@ impl PxKind {
Self::JpegXl => return decode_jpegxl(src),
};

Ok(image::load_from_memory_with_format(src, fmt)?)
// Let the image crate sort it out.
let mut dec = ImageReader::with_format(Cursor::new(src), fmt);
dec.no_limits();
dec.decode().map_err(Into::into)
}

/// # Guess Format.
Expand Down Expand Up @@ -198,6 +230,16 @@ impl PxImage {
let width = NonZeroU32::new(img.width()).ok_or(PxsumError::Dimensions)?;
let height = NonZeroU32::new(img.height()).ok_or(PxsumError::Dimensions)?;

// Figure out how many bytes the RGBA pixel data _should_ take up
// given the dimensions.
let expected_len: NonZeroUsize = {
let w = NonZeroUsize::try_from(width).map_err(|_| PxsumError::Dimensions)?;
let h = NonZeroUsize::try_from(height).map_err(|_| PxsumError::Dimensions)?;
w.checked_mul(h)
.and_then(|r| r.checked_mul(RGBA_SIZE))
.ok_or(PxsumError::Dimensions)?
};

// If we know there's no alpha channel in the original, make a note of
// it as it can save us some time later on.
let no_alpha = matches!(img,
Expand All @@ -212,9 +254,11 @@ impl PxImage {
let buf: Vec<u8> = img.into_rgba8().into_vec();
let len = buf.len();

// Check the counts, but we should be good here.
if len == 0 { Err(PxsumError::NoData) }
else if len % 4 == 0 { Ok(Self { buf, no_alpha, width, height }) }
// Check the counts, but we're probably good if we made it this far.
if len == expected_len.get() {
Ok(Self { buf, no_alpha, width, height })
}
else if len == 0 { Err(PxsumError::NoData) }
else { Err(PxsumError::Decode) }
}

Expand All @@ -229,7 +273,7 @@ impl PxImage {
// color drift won't affect the checksum.
if ! strict && ! no_alpha {
let mut i = Wrapping(0_u32);
for chunk in buf.chunks_exact_mut(4) {
for chunk in buf.chunks_exact_mut(RGBA_SIZE.get()) {
if chunk[3] == 0 {
chunk.copy_from_slice(i.0.to_le_bytes().as_slice());
}
Expand Down Expand Up @@ -334,4 +378,22 @@ mod test {
);
}
}

#[test]
fn t_animation() {
// Make sure these all fail.
for (path, fmt) in [
("skel/assets/flyfuk.gif", PxKind::Gif),
("skel/assets/flyfuk.png", PxKind::Png),
("skel/assets/flyfuk.webp", PxKind::WebP),
] {
let Ok(raw) = std::fs::read(path) else {
panic!("Unable to open {path}.");
};
match PxImage::new(&raw, fmt) {
Ok(_) => panic!("Animation should fail: {path}"),
Err(e) => assert_eq!(e, PxsumError::Animation),
}
}
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ fn crunch_paths(paths: &[OsString], settings: Settings)
ANY.store(true, Relaxed);
println!("{chk}");
},
Err(PxsumError::Path | PxsumError::NoData) => {}, // Silently ignore.
Err(PxsumError::Animation | PxsumError::Path | PxsumError::NoData) => {}, // Silently ignore.
Err(_) => if print_warnings {
let mut src = Cow::Borrowed(chk.src());
if src.is_empty() { src = p.to_string_lossy(); }
Expand Down

0 comments on commit 2b5a246

Please sign in to comment.