mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-31 05:46:46 -06:00
A density now counts only within 0.005 of a whole 2x-4x multiple of 72 dpi. That covers the rounding of pixels per metre (144 dpi is stored as 5669 or 5670) and rejects nearby resolutions: 141, 143 and 145 dpi were read as 2x under the old 0.05. Paste falls back to the decoded clipboard image when the clipboard's PNG can't be read, instead of pasting nothing. Also the line on what the objc2 crates are for, and the ROADMAP row for the new placing default.
42 lines
1.6 KiB
Rust
42 lines
1.6 KiB
Rust
const PNG_SIGNATURE: &[u8] = b"\x89PNG\r\n\x1a\n";
|
|
const POINTS_PER_INCH: f64 = 72.0;
|
|
const METERS_PER_INCH: f64 = 0.0254;
|
|
const UNIT_METER: u8 = 1;
|
|
const MAX_DENSITY: f64 = 4.0;
|
|
const DENSITY_TOLERANCE: f64 = 0.005;
|
|
|
|
pub fn pixel_density(bytes: &[u8]) -> f64 {
|
|
png_pixels_per_meter(bytes).map_or(1.0, density_from_pixels_per_meter)
|
|
}
|
|
|
|
pub fn placed_size(pixel_width: u32, pixel_height: u32, density: f64, max_side: f64) -> (f64, f64) {
|
|
let (w, h) = (f64::from(pixel_width) / density, f64::from(pixel_height) / density);
|
|
let fit = (max_side / w.max(h)).min(1.0);
|
|
(w * fit, h * fit)
|
|
}
|
|
|
|
fn density_from_pixels_per_meter(pixels_per_meter: u32) -> f64 {
|
|
let ratio = f64::from(pixels_per_meter) * METERS_PER_INCH / POINTS_PER_INCH;
|
|
let whole = ratio.round();
|
|
if (2.0..=MAX_DENSITY).contains(&whole) && (ratio - whole).abs() < DENSITY_TOLERANCE { whole } else { 1.0 }
|
|
}
|
|
|
|
fn png_pixels_per_meter(bytes: &[u8]) -> Option<u32> {
|
|
let mut rest = bytes.strip_prefix(PNG_SIGNATURE)?;
|
|
while rest.len() >= 12 {
|
|
let length = usize::try_from(u32::from_be_bytes(rest[..4].try_into().ok()?)).ok()?;
|
|
let kind = &rest[4..8];
|
|
let data = rest.get(8..8usize.checked_add(length)?)?;
|
|
match kind {
|
|
b"pHYs" if data.len() == 9 => {
|
|
let x = u32::from_be_bytes(data[..4].try_into().ok()?);
|
|
let y = u32::from_be_bytes(data[4..8].try_into().ok()?);
|
|
return (data[8] == UNIT_METER && x == y).then_some(x);
|
|
}
|
|
b"IDAT" | b"IEND" => return None,
|
|
_ => rest = rest.get(12usize.checked_add(length)?..)?,
|
|
}
|
|
}
|
|
None
|
|
}
|