use super::common::*;
use image::{DynamicImage, ImageFormat, RgbImage, RgbaImage};
use std::io::Cursor;
fn write_temp(img: &DynamicImage, fmt: ImageFormat, name: &str) -> String {
let dir = std::env::temp_dir();
let ext = match fmt {
ImageFormat::Png => "png",
ImageFormat::Jpeg => "jpg",
_ => "img",
};
let path = dir.join(format!("m2p_w7e_{}.{}", name, ext));
let mut buf = Vec::new();
img.write_to(&mut Cursor::new(&mut buf), fmt)
.expect("encode test image");
std::fs::write(&path, buf).expect("write test image");
path.to_string_lossy().to_string()
}
fn render_md(md: &str) -> Vec<u8> {
render(md, "")
}
mod valid_images {
use super::*;
#[test]
fn small_rgb_png_renders() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
32,
32,
image::Rgb([10, 120, 200]),
));
let p = write_temp(&img, ImageFormat::Png, "small_rgb");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
assert!(
!contains(&bytes, b"[image: blue square]"),
"valid PNG fell back to alt text"
);
let _ = std::fs::remove_file(&p);
}
#[test]
fn small_jpeg_renders() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
48,
24,
image::Rgb([200, 30, 30]),
));
let p = write_temp(&img, ImageFormat::Jpeg, "small_jpeg");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
assert!(!contains(&bytes, b"[image: red]"));
let _ = std::fs::remove_file(&p);
}
#[test]
fn rgba_png_with_transparency_does_not_crash() {
let mut rgba = RgbaImage::new(40, 40);
for (x, _y, px) in rgba.enumerate_pixels_mut() {
*px = if x < 20 {
image::Rgba([0, 200, 0, 255])
} else {
image::Rgba([0, 0, 0, 0])
};
}
let img = DynamicImage::ImageRgba8(rgba);
let p = write_temp(&img, ImageFormat::Png, "rgba");
let bytes = render_md(&format!("\n", p));
assert!(
pdf_well_formed(&bytes),
"RGBA PNG with transparency broke the PDF"
);
let _ = std::fs::remove_file(&p);
}
#[test]
fn grayscale_png_does_not_crash() {
let img = DynamicImage::ImageLuma8(image::GrayImage::from_pixel(
30,
30,
image::Luma([128]),
));
let p = write_temp(&img, ImageFormat::Png, "gray");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
let _ = std::fs::remove_file(&p);
}
}
mod degenerate_and_hostile {
use super::*;
#[test]
fn one_by_one_pixel_image() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
1,
1,
image::Rgb([255, 255, 255]),
));
let p = write_temp(&img, ImageFormat::Png, "one_px");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
let _ = std::fs::remove_file(&p);
}
#[test]
fn corrupt_png_bytes_fall_back_to_alt() {
let dir = std::env::temp_dir();
let path = dir.join("m2p_w7e_corrupt.png");
let mut bytes = vec![0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
bytes.extend(std::iter::repeat(0xAB).take(200));
std::fs::write(&path, &bytes).unwrap();
let pdf = render_md(&format!(
"\n",
path.to_string_lossy()
));
assert!(pdf_well_formed(&pdf));
assert!(
contains(&pdf, b"broken image") || contains_text(&pdf, "broken image"),
"corrupt image should fall back to its alt text"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn truncated_jpeg_does_not_crash() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
64,
64,
image::Rgb([1, 2, 3]),
));
let mut buf = Vec::new();
img.write_to(&mut Cursor::new(&mut buf), ImageFormat::Jpeg)
.unwrap();
buf.truncate(buf.len() / 2); let path = std::env::temp_dir().join("m2p_w7e_trunc.jpg");
std::fs::write(&path, &buf).unwrap();
let pdf = render_md(&format!("\n", path.to_string_lossy()));
assert!(pdf_well_formed(&pdf));
let _ = std::fs::remove_file(&path);
}
#[test]
fn zero_byte_file_falls_back() {
let path = std::env::temp_dir().join("m2p_w7e_empty.png");
std::fs::write(&path, []).unwrap();
let pdf = render_md(&format!(
"\n",
path.to_string_lossy()
));
assert!(pdf_well_formed(&pdf));
assert!(
contains(&pdf, b"empty file") || contains_text(&pdf, "empty file"),
"0-byte image should fall back to alt text"
);
let _ = std::fs::remove_file(&path);
}
}
mod dimension_bounding {
use super::*;
#[test]
fn oversized_image_is_downscaled_not_rejected() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
6000,
100,
image::Rgb([90, 90, 90]),
));
let p = write_temp(&img, ImageFormat::Png, "wide");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
assert!(
!contains(&bytes, b"[image: huge]"),
"oversized image must downscale, not fall back"
);
assert!(
bytes.len() < 4_000_000,
"downscaled-image PDF unexpectedly large: {} bytes",
bytes.len()
);
let _ = std::fs::remove_file(&p);
}
#[test]
fn tall_oversized_image_downscaled() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
80,
5000,
image::Rgb([12, 34, 56]),
));
let p = write_temp(&img, ImageFormat::Png, "tall");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
assert!(!contains(&bytes, b"[image: tall]"));
let _ = std::fs::remove_file(&p);
}
#[test]
fn image_exactly_at_ceiling_renders() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
4000,
10,
image::Rgb([7, 7, 7]),
));
let p = write_temp(&img, ImageFormat::Png, "at_ceiling");
let bytes = render_md(&format!("\n", p));
assert!(pdf_well_formed(&bytes));
let _ = std::fs::remove_file(&p);
}
}
mod html_img_paths {
use super::*;
#[test]
fn html_img_with_real_local_file_renders() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(
20,
20,
image::Rgb([0, 0, 0]),
));
let p = write_temp(&img, ImageFormat::Png, "html_local");
let bytes = render_md(&format!("<img src=\"{}\" alt=\"x\">\n", p));
assert!(pdf_well_formed(&bytes));
assert!(!contains(&bytes, b"[image: x]"));
let _ = std::fs::remove_file(&p);
}
}