#![cfg_attr(feature = "cargo-clippy", allow(string_lit_as_bytes))]
use crate::OffsetDateTime;
#[cfg(feature = "embedded_images")]
use image_crate::{DynamicImage, GenericImageView, ImageDecoder, ImageError};
use lopdf;
use lopdf::Stream as LoPdfStream;
use std::collections::HashMap;
use {ColorBits, ColorSpace, CurTransMat, Px};
#[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 Into<lopdf::Object> for XObject {
fn into(self)
-> lopdf::Object
{
match self {
XObject::Image(image) => { lopdf::Object::Stream(Self::compress_stream(image.into())) },
XObject::Form(form) => { let cur_form: FormXObject = *form; lopdf::Object::Stream(Self::compress_stream(cur_form.into())) },
XObject::PostScript(ps) => { lopdf::Object::Stream(Self::compress_stream(ps.into())) },
XObject::External(stream) => { lopdf::Object::Stream(Self::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
}
#[cfg_attr(feature = "cargo-clippy", allow(needless_return))]
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.to_string(), 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<'a> ImageXObject {
#[cfg_attr(feature = "cargo-clippy", allow(too_many_arguments))]
pub fn new(width: Px, height: Px, color_space: ColorSpace,
bits: ColorBits, interpolate: bool, image_filter: Option<ImageFilter>,
bbox: Option<CurTransMat>, data: Vec<u8>)
-> Self
{
Self {
width,
height,
color_space,
bits_per_component: bits,
interpolate,
image_data: data,
image_filter,
clipping_bbox: bbox,
}
}
#[cfg(feature = "embedded_images")]
pub fn try_from<T: ImageDecoder<'a>>(image: T)
-> Result<Self, ImageError>
{
use std::usize;
use image_crate::error::{LimitError, LimitErrorKind};
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: 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.to_bytes();
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: color_space,
bits_per_component: color_bits,
image_data: data,
interpolate: true,
image_filter: None,
clipping_bbox: None,
}
}
}
impl Into<lopdf::Stream> for ImageXObject {
fn into(self)
-> lopdf::Stream
{
use lopdf::Object::*;
use std::iter::FromIterator;
let cs: &'static str = self.color_space.into();
let bbox: lopdf::Object = self.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(self.width.0 as i64)),
("Height", Integer(self.height.0 as i64)),
("Interpolate", self.interpolate.into()),
("BitsPerComponent", Integer(self.bits_per_component.into())),
("ColorSpace", Name(cs.as_bytes().to_vec())),
("BBox", bbox),
]);
if let Some(filter) = self.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, self.image_data)
}
}
#[derive(Debug)]
pub struct ImageXObjectRef {
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 Into<lopdf::Stream> for FormXObject {
fn into(self)
-> lopdf::Stream
{
use std::iter::FromIterator;
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(self.form_type.into())),
]);
lopdf::Stream::new(dict, self.bytes)
}
}
#[derive(Debug, Clone)]
pub struct FormXObjectRef {
name: String,
}
#[derive(Debug, Copy, Clone)]
pub enum FormType {
Type1,
}
impl Into<i64> for FormType {
fn into(self)
-> i64
{
match self {
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 {
level1: Option<Vec<u8>>,
}
impl Into<lopdf::Stream> for PostScriptXObject {
fn into(self)
-> lopdf::Stream
{
lopdf::Stream::new(lopdf::Dictionary::new(), Vec::new())
}
}