#![cfg_attr(feature = "cargo-clippy", allow(clippy::string_lit_as_bytes))]
use crate::OffsetDateTime;
use crate::{ColorBits, ColorSpace, CurTransMat, Px};
#[cfg(feature = "embedded_images")]
use image_crate::{DynamicImage, GenericImageView, ImageDecoder, ImageError};
use lopdf;
use lopdf::Stream as LoPdfStream;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum XObject {
Image(ImageXObject),
Form(Box<FormXObject>),
PostScript(PostScriptXObject),
External(LoPdfStream),
}
impl From<ImageXObject> for XObject {
fn from(i: ImageXObject) -> XObject {
XObject::Image(i)
}
}
impl XObject {
#[cfg(any(debug_assertions, feature = "less-optimization"))]
#[inline]
fn compress_stream(stream: lopdf::Stream) -> lopdf::Stream {
stream
}
#[cfg(all(not(debug_assertions), not(feature = "less-optimization")))]
#[inline]
fn compress_stream(mut stream: lopdf::Stream) -> lopdf::Stream {
let _ = stream.compress();
stream
}
}
impl From<XObject> for lopdf::Object {
fn from(val: XObject) -> Self {
match val {
XObject::Image(image) => lopdf::Object::Stream(XObject::compress_stream(image.into())),
XObject::Form(form) => {
let cur_form: FormXObject = *form;
lopdf::Object::Stream(XObject::compress_stream(cur_form.into()))
}
XObject::PostScript(ps) => lopdf::Object::Stream(XObject::compress_stream(ps.into())),
XObject::External(stream) => lopdf::Object::Stream(XObject::compress_stream(stream)),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct XObjectList {
objects: HashMap<String, XObject>,
}
impl XObjectList {
pub fn new() -> Self {
Self::default()
}
pub fn add_xobject(&mut self, xobj: XObject) -> XObjectRef {
let len = self.objects.len();
let xobj_ref = XObjectRef::new(len);
self.objects.insert(xobj_ref.name.clone(), xobj);
xobj_ref
}
pub fn into_with_document(self, doc: &mut lopdf::Document) -> lopdf::Dictionary {
self.objects
.into_iter()
.map(|(name, object)| {
let obj: lopdf::Object = object.into();
let obj_ref = doc.add_object(obj);
(name, lopdf::Object::Reference(obj_ref))
})
.collect()
}
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct XObjectRef {
pub(crate) name: String,
}
impl XObjectRef {
pub fn new(index: usize) -> Self {
Self {
name: format!("X{index}"),
}
}
}
#[derive(Debug, Clone)]
pub struct ImageXObject {
pub width: Px,
pub height: Px,
pub color_space: ColorSpace,
pub bits_per_component: ColorBits,
pub interpolate: bool,
pub image_data: Vec<u8>,
pub image_filter: Option<ImageFilter>,
pub clipping_bbox: Option<CurTransMat>,
}
impl ImageXObject {
#[cfg(feature = "embedded_images")]
pub fn try_from<'a, T: ImageDecoder<'a>>(image: T) -> Result<Self, ImageError> {
use image_crate::error::{LimitError, LimitErrorKind};
use std::usize;
let dim = image.dimensions();
let color_type = image.color_type();
let num_image_bytes = image.total_bytes();
if num_image_bytes > usize::MAX as u64 {
return Err(ImageError::Limits(LimitError::from_kind(
LimitErrorKind::InsufficientMemory,
)));
}
let mut image_data = vec![0; num_image_bytes as usize];
image.read_image(&mut image_data)?;
let color_bits = ColorBits::from(color_type);
let color_space = ColorSpace::from(color_type);
Ok(Self {
width: Px(dim.0 as usize),
height: Px(dim.1 as usize),
color_space,
bits_per_component: color_bits,
image_data,
interpolate: true,
image_filter: None,
clipping_bbox: None,
})
}
#[cfg(feature = "embedded_images")]
pub fn from_dynamic_image(image: &DynamicImage) -> Self {
let dim = image.dimensions();
let color_type = image.color();
let data = image.as_bytes().to_vec();
let color_bits = ColorBits::from(color_type);
let color_space = ColorSpace::from(color_type);
Self {
width: Px(dim.0 as usize),
height: Px(dim.1 as usize),
color_space,
bits_per_component: color_bits,
image_data: data,
interpolate: true,
image_filter: None,
clipping_bbox: None,
}
}
}
impl From<ImageXObject> for lopdf::Stream {
fn from(img: ImageXObject) -> lopdf::Stream {
use lopdf::Object::*;
let cs: &'static str = img.color_space.into();
let bbox: lopdf::Object = img.clipping_bbox.unwrap_or(CurTransMat::Identity).into();
let mut dict = lopdf::Dictionary::from_iter(vec![
("Type", Name("XObject".as_bytes().to_vec())),
("Subtype", Name("Image".as_bytes().to_vec())),
("Width", Integer(img.width.0 as i64)),
("Height", Integer(img.height.0 as i64)),
("Interpolate", img.interpolate.into()),
("BitsPerComponent", Integer(img.bits_per_component.into())),
("ColorSpace", Name(cs.as_bytes().to_vec())),
("BBox", bbox),
]);
if let Some(filter) = img.image_filter {
let params = match filter {
ImageFilter::DCT => {
vec![
("Filter", Array(vec![Name("DCTDecode".as_bytes().to_vec())])),
(
"DecodeParams",
Dictionary(lopdf::dictionary!("ColorTransform" => Integer(0))),
),
]
}
_ => unimplemented!("Encountered filter type is not supported"),
};
params
.into_iter()
.for_each(|param| dict.set(param.0, param.1));
}
lopdf::Stream::new(dict, img.image_data)
}
}
#[derive(Debug)]
pub struct ImageXObjectRef {
#[allow(dead_code)]
name: String,
}
#[derive(Debug, Copy, Clone)]
pub enum ImageFilter {
Ascii85,
Lzw,
DCT,
JPX,
}
#[derive(Debug, Clone)]
pub struct FormXObject {
pub form_type: FormType,
pub bytes: Vec<u8>,
pub matrix: Option<CurTransMat>,
pub resources: Option<lopdf::Dictionary>,
pub group: Option<GroupXObject>,
pub ref_dict: Option<lopdf::Dictionary>,
pub metadata: Option<lopdf::Stream>,
pub piece_info: Option<lopdf::Dictionary>,
pub last_modified: Option<OffsetDateTime>,
pub struct_parent: Option<i64>,
pub struct_parents: Option<i64>,
pub opi: Option<lopdf::Dictionary>,
pub oc: Option<lopdf::Dictionary>,
pub name: Option<String>,
}
impl From<FormXObject> for lopdf::Stream {
fn from(val: FormXObject) -> Self {
use lopdf::Object::*;
let dict = lopdf::Dictionary::from_iter(vec![
("Type", Name("XObject".as_bytes().to_vec())),
("Subtype", Name("Form".as_bytes().to_vec())),
("FormType", Integer(val.form_type.into())),
]);
lopdf::Stream::new(dict, val.bytes)
}
}
#[derive(Debug, Clone)]
pub struct FormXObjectRef {
#[allow(dead_code)]
name: String,
}
#[derive(Debug, Copy, Clone)]
pub enum FormType {
Type1,
}
impl From<FormType> for i64 {
fn from(val: FormType) -> Self {
match val {
FormType::Type1 => 1,
}
}
}
#[derive(Debug)]
pub struct SMask {
pub width: i64,
pub height: i64,
pub interpolate: bool,
pub bits_per_component: i64,
pub matte: Vec<i64>,
}
#[derive(Debug, Copy, Clone)]
pub struct GroupXObject {
}
#[derive(Debug, Copy, Clone)]
pub enum GroupXObjectType {
TransparencyGroup,
}
#[derive(Debug)]
pub struct ReferenceXObject {
pub file: Vec<u8>,
pub page: i64,
pub id: [i64; 2],
}
#[derive(Debug)]
pub struct OptionalContentGroup {
pub name: String,
pub intent: Vec<OCGIntent>,
pub usage: Option<lopdf::Dictionary>,
}
#[derive(Debug, Copy, Clone)]
pub enum OCGIntent {
View,
Design,
}
#[derive(Debug, Clone)]
pub struct PostScriptXObject {
#[allow(dead_code)]
level1: Option<Vec<u8>>,
}
impl From<PostScriptXObject> for lopdf::Stream {
fn from(_val: PostScriptXObject) -> Self {
lopdf::Stream::new(lopdf::Dictionary::new(), Vec::new())
}
}