use crate::object::{PdfDict, PdfObject, PdfStream};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ColorSpace {
Rgb,
Grayscale,
Cmyk,
}
impl ColorSpace {
pub fn pdf_name(&self) -> &'static str {
match self {
ColorSpace::Rgb => "DeviceRGB",
ColorSpace::Grayscale => "DeviceGray",
ColorSpace::Cmyk => "DeviceCMYK",
}
}
pub fn components(&self) -> usize {
match self {
ColorSpace::Rgb => 3,
ColorSpace::Grayscale => 1,
ColorSpace::Cmyk => 4,
}
}
}
#[derive(Debug, Clone)]
pub struct PdfImage {
pub width: u32,
pub height: u32,
pub color_space: ColorSpace,
pub bits_per_component: u8,
pub data: Vec<u8>,
pub encoding: ImageEncoding,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ImageEncoding {
Raw,
Jpeg,
Flate,
}
impl PdfImage {
pub fn from_rgb(width: u32, height: u32, data: Vec<u8>) -> Self {
Self {
width,
height,
color_space: ColorSpace::Rgb,
bits_per_component: 8,
data,
encoding: ImageEncoding::Raw,
}
}
pub fn from_gray(width: u32, height: u32, data: Vec<u8>) -> Self {
Self {
width,
height,
color_space: ColorSpace::Grayscale,
bits_per_component: 8,
data,
encoding: ImageEncoding::Raw,
}
}
pub fn from_jpeg(width: u32, height: u32, color_space: ColorSpace, data: Vec<u8>) -> Self {
Self {
width,
height,
color_space,
bits_per_component: 8,
data,
encoding: ImageEncoding::Jpeg,
}
}
pub fn parse_jpeg_header(data: &[u8]) -> Result<(u32, u32, ColorSpace), String> {
if data.len() < 4 || data[0] != 0xFF || data[1] != 0xD8 {
return Err("Not a valid JPEG file".into());
}
let mut i = 2;
while i + 4 <= data.len() {
if data[i] != 0xFF {
return Err(format!("Invalid JPEG marker at offset {}", i));
}
let marker = data[i + 1];
let length = u16::from_be_bytes([data[i + 2], data[i + 3]]) as usize;
if matches!(marker, 0xC0 | 0xC1 | 0xC2) && i + 9 <= data.len() {
let height = u16::from_be_bytes([data[i + 5], data[i + 6]]) as u32;
let width = u16::from_be_bytes([data[i + 7], data[i + 8]]) as u32;
let components = data[i + 9];
let cs = match components {
1 => ColorSpace::Grayscale,
3 => ColorSpace::Rgb,
4 => ColorSpace::Cmyk,
n => return Err(format!("Unsupported JPEG component count: {}", n)),
};
return Ok((width, height, cs));
}
i += 2 + length;
}
Err("Could not find JPEG SOF marker".into())
}
pub fn from_jpeg_bytes(data: Vec<u8>) -> Result<Self, String> {
let (width, height, cs) = Self::parse_jpeg_header(&data)?;
Ok(Self::from_jpeg(width, height, cs, data))
}
pub fn to_xobject_stream(&self) -> PdfStream {
let mut dict = PdfDict::new();
dict.set("Type", PdfObject::name("XObject"));
dict.set("Subtype", PdfObject::name("Image"));
dict.set("Width", PdfObject::Integer(self.width as i64));
dict.set("Height", PdfObject::Integer(self.height as i64));
dict.set("ColorSpace", PdfObject::name(self.color_space.pdf_name()));
dict.set("BitsPerComponent", PdfObject::Integer(self.bits_per_component as i64));
dict.set("Length", PdfObject::Integer(self.data.len() as i64));
match self.encoding {
ImageEncoding::Jpeg => {
dict.set("Filter", PdfObject::name("DCTDecode"));
}
ImageEncoding::Flate => {
dict.set("Filter", PdfObject::name("FlateDecode"));
}
ImageEncoding::Raw => {
}
}
PdfStream { dict, data: self.data.clone() }
}
}
#[derive(Debug, Clone, Copy)]
pub struct CropRect {
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
}
impl CropRect {
pub fn new(x: u32, y: u32, width: u32, height: u32) -> Self {
Self { x, y, width, height }
}
}
#[derive(Debug, Clone)]
pub struct ImageBorder {
pub color: crate::color::Color,
pub width: f64,
pub radius: f64,
}
impl ImageBorder {
pub fn solid(color: crate::color::Color, width: f64) -> Self {
Self { color, width, radius: 0.0 }
}
pub fn rounded(color: crate::color::Color, width: f64, radius: f64) -> Self {
Self { color, width, radius }
}
}
#[derive(Debug, Clone)]
pub struct DropShadow {
pub offset_x: f64,
pub offset_y: f64,
pub blur_spread: f64, pub color: crate::color::Color,
pub opacity: f64,
}
impl DropShadow {
pub fn new(offset_x: f64, offset_y: f64) -> Self {
Self {
offset_x,
offset_y,
blur_spread: 4.0,
color: crate::color::Color::rgb_u8(0, 0, 0),
opacity: 0.3,
}
}
pub fn color(mut self, c: crate::color::Color) -> Self { self.color = c; self }
pub fn spread(mut self, s: f64) -> Self { self.blur_spread = s; self }
pub fn opacity(mut self, o: f64) -> Self { self.opacity = o.clamp(0.0, 1.0); self }
}
#[derive(Debug, Clone, Default)]
pub struct ImageOptions {
pub crop: Option<CropRect>,
pub border: Option<ImageBorder>,
pub shadow: Option<DropShadow>,
pub opacity: Option<f64>,
}
impl ImageOptions {
pub fn new() -> Self { Self::default() }
pub fn crop(mut self, c: CropRect) -> Self { self.crop = Some(c); self }
pub fn border(mut self, b: ImageBorder) -> Self { self.border = Some(b); self }
pub fn shadow(mut self, s: DropShadow) -> Self { self.shadow = Some(s); self }
pub fn opacity(mut self, o: f64) -> Self { self.opacity = Some(o.clamp(0.0, 1.0)); self }
}