#![forbid(unsafe_code)]
#![deny(missing_docs)]
#![allow(clippy::wrong_self_convention)]
#[macro_use]
mod macros;
mod actions;
mod annotations;
mod attributes;
mod buf;
mod chunk;
mod color;
mod content;
mod files;
mod font;
mod forms;
mod functions;
mod object;
mod renditions;
mod renumber;
mod structure;
mod transitions;
mod xobject;
pub mod writers {
use super::*;
pub use actions::{Action, AdditionalActions, Fields};
pub use annotations::{
Annotation, Appearance, AppearanceCharacteristics, AppearanceEntry, BorderStyle,
IconFit,
};
pub use attributes::{
ArtifactAttributes, Attributes, FENoteAttributes, FieldAttributes,
LayoutAttributes, ListAttributes, TableAttributes, TrackSizes, UserProperty,
};
pub use color::{
ColorSpace, DeviceN, DeviceNAttrs, DeviceNMixingHints, DeviceNProcess,
FunctionShading, IccProfile, OutputIntent, Separation, SeparationInfo,
ShadingPattern, StreamShading, StreamShadingType, TilingPattern,
};
pub use content::{
Artifact, ExtGraphicsState, MarkContent, Operation, PositionedItems,
PropertyList, Resources, ShowPositioned, SoftMask,
};
pub use files::{EmbeddedFile, EmbeddingParams, FileSpec};
pub use font::{
CidFont, Cmap, Differences, Encoding, FontDescriptor, FontDescriptorOverride,
Type0Font, Type1Font, Type3Font, WMode, Widths,
};
pub use forms::{Field, Form};
pub use functions::{
ExponentialFunction, PostScriptFunction, SampledFunction, StitchingFunction,
};
pub use object::{
DecodeParms, NameTree, NameTreeEntries, NumberTree, NumberTreeEntries,
};
pub use renditions::{MediaClip, MediaPermissions, MediaPlayParams, Rendition};
pub use structure::{
Catalog, ClassMap, Destination, DeveloperExtension, DocumentInfo, MarkInfo,
MarkedRef, Metadata, Names, Namespace, NamespaceRoleMap, ObjectRef, Outline,
OutlineItem, Page, PageLabel, Pages, RoleMap, StructChildren, StructElement,
StructTreeRoot, ViewerPreferences,
};
pub use transitions::Transition;
pub use xobject::{FormXObject, Group, ImageXObject, Reference};
}
pub mod types {
use super::*;
pub use actions::{ActionType, FormActionFlags, RenditionOperation};
pub use annotations::{
AnnotationFlags, AnnotationIcon, AnnotationType, BorderType, HighlightEffect,
IconScale, IconScaleType, TextPosition,
};
pub use attributes::{
AttributeOwner, BlockAlign, FieldRole, FieldState, GlyphOrientationVertical,
InlineAlign, LayoutBorderStyle, LayoutTextPosition, LineHeight, ListNumbering,
NoteType, Placement, RubyAlign, RubyPosition, Sides, TableHeaderScope, TextAlign,
TextDecorationType, WritingMode,
};
pub use color::{
DeviceNSubtype, FunctionShadingType, OutputIntentSubtype, PaintType, TilingType,
};
pub use content::{
ArtifactAttachment, ArtifactSubtype, ArtifactType, BlendMode, ColorSpaceOperand,
LineCapStyle, LineJoinStyle, MaskType, OverprintMode, ProcSet, RenderingIntent,
TextRenderingMode,
};
pub use files::AssociationKind;
pub use font::{
CidFontType, CjkClass, FontFlags, FontStretch, GlyphId, SystemInfo, UnicodeCmap,
};
pub use forms::{
CheckBoxState, ChoiceOptions, FieldFlags, FieldType, Quadding, SigFlags,
};
pub use functions::{InterpolationOrder, PostScriptOp};
pub use object::Predictor;
pub use renditions::{MediaClipType, RenditionType, TempFileType};
pub use structure::{
BlockLevelRoleSubtype, Direction, InlineLevelRoleSubtype,
InlineLevelRoleSubtype2, NumberingStyle, OutlineItemFlags, PageLayout, PageMode,
PhoneticAlphabet, RoleMapOpts, StructRole, StructRole2, StructRole2Compat,
StructRoleType, StructRoleType2, TabOrder, TrappingStatus,
};
pub use transitions::{TransitionAngle, TransitionStyle};
pub use xobject::SMaskInData;
}
pub use self::buf::{Buf, Limits};
pub use self::chunk::Chunk;
pub use self::content::Content;
pub use self::object::{
Array, Date, Dict, Filter, Finish, LanguageIdentifier, Name, Null, Obj, Primitive,
Rect, Ref, Rewrite, Str, Stream, TextStr, TextStrLike, TextStrWithLang, TypedArray,
TypedDict, Writer,
};
use std::fmt::{self, Debug, Formatter};
use std::io::Write;
use std::ops::{Deref, DerefMut};
use self::writers::*;
pub struct Pdf {
chunk: Chunk,
catalog_id: Option<Ref>,
info_id: Option<Ref>,
file_id: Option<(Vec<u8>, Vec<u8>)>,
}
impl Pdf {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self::with_capacity(8 * 1024)
}
pub fn with_capacity(capacity: usize) -> Self {
let mut chunk = Chunk::with_capacity(capacity);
chunk.buf.extend(b"%PDF-1.7\n%\x80\x80\x80\x80\n\n");
Self {
chunk,
catalog_id: None,
info_id: None,
file_id: None,
}
}
pub fn set_binary_marker(&mut self, marker: &[u8; 4]) {
self.chunk.buf.inner[10..14].copy_from_slice(marker);
}
pub fn set_version(&mut self, major: u8, minor: u8) {
if major < 10 {
self.chunk.buf.inner[5] = b'0' + major;
}
if minor < 10 {
self.chunk.buf.inner[7] = b'0' + minor;
}
}
pub fn set_file_id(&mut self, id: (Vec<u8>, Vec<u8>)) {
self.file_id = Some(id);
}
pub fn catalog(&mut self, id: Ref) -> Catalog<'_> {
self.catalog_id = Some(id);
self.indirect(id).start()
}
pub fn document_info(&mut self, id: Ref) -> DocumentInfo<'_> {
self.info_id = Some(id);
self.indirect(id).start()
}
pub fn finish(self) -> Vec<u8> {
let Chunk { mut buf, mut offsets } = self.chunk;
offsets.sort();
let xref_len = 1 + offsets.last().map_or(0, |p| p.0.get());
let xref_offset = buf.len();
buf.extend(b"xref\n0 ");
buf.push_int(xref_len);
buf.push(b'\n');
if offsets.is_empty() {
write!(buf.inner, "0000000000 65535 f\r\n").unwrap();
}
let mut written = 0;
for (i, (object_id, offset)) in offsets.iter().enumerate() {
if written > object_id.get() {
panic!("duplicate indirect reference id: {}", object_id.get());
}
let start = written;
for free_id in start..object_id.get() {
let mut next = free_id + 1;
if next == object_id.get() {
for (used_id, _) in &offsets[i..] {
if next < used_id.get() {
break;
} else {
next = used_id.get() + 1;
}
}
}
let gen = if free_id == 0 { "65535" } else { "00000" };
write!(buf.inner, "{:010} {} f\r\n", next % xref_len, gen).unwrap();
written += 1;
}
write!(buf.inner, "{offset:010} 00000 n\r\n").unwrap();
written += 1;
}
buf.extend(b"trailer\n");
let mut trailer = Obj::direct(&mut buf, 0).dict();
trailer.pair(Name(b"Size"), xref_len);
if let Some(catalog_id) = self.catalog_id {
trailer.pair(Name(b"Root"), catalog_id);
}
if let Some(info_id) = self.info_id {
trailer.pair(Name(b"Info"), info_id);
}
if let Some(file_id) = self.file_id {
let mut ids = trailer.insert(Name(b"ID")).array();
ids.item(Str(&file_id.0));
ids.item(Str(&file_id.1));
}
trailer.finish();
buf.extend(b"\nstartxref\n");
write!(buf.inner, "{xref_offset}").unwrap();
buf.extend(b"\n%%EOF");
buf.into_vec()
}
}
impl Debug for Pdf {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.pad("Pdf(..)")
}
}
impl Deref for Pdf {
type Target = Chunk;
fn deref(&self) -> &Self::Target {
&self.chunk
}
}
impl DerefMut for Pdf {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.chunk
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(unused)]
pub fn print_chunk(chunk: &Chunk) {
println!("========== Chunk ==========");
for &(id, offset) in &chunk.offsets {
println!("[{}]: {}", id.get(), offset);
}
println!("---------------------------");
print!("{}", String::from_utf8_lossy(&chunk.buf));
println!("===========================");
}
pub fn slice<F>(f: F) -> Vec<u8>
where
F: FnOnce(&mut Pdf),
{
let mut w = Pdf::new();
let start = w.len();
f(&mut w);
let end = w.len();
let buf = w.finish();
buf[start..end].to_vec()
}
pub fn slice_obj<F>(f: F) -> Vec<u8>
where
F: FnOnce(Obj<'_>),
{
let buf = slice(|w| f(w.indirect(Ref::new(1))));
buf[8..buf.len() - 9].to_vec()
}
#[test]
fn test_minimal() {
let w = Pdf::new();
test!(
w.finish(),
b"%PDF-1.7\n%\x80\x80\x80\x80\n",
b"xref\n0 1\n0000000000 65535 f\r",
b"trailer\n<<\n /Size 1\n>>",
b"startxref\n16\n%%EOF",
);
}
#[test]
fn test_xref_free_list_short() {
let mut w = Pdf::new();
w.indirect(Ref::new(1)).primitive(1);
w.indirect(Ref::new(2)).primitive(2);
test!(
w.finish(),
b"%PDF-1.7\n%\x80\x80\x80\x80\n",
b"1 0 obj\n1\nendobj\n",
b"2 0 obj\n2\nendobj\n",
b"xref",
b"0 3",
b"0000000000 65535 f\r",
b"0000000016 00000 n\r",
b"0000000034 00000 n\r",
b"trailer",
b"<<\n /Size 3\n>>",
b"startxref\n52\n%%EOF",
)
}
#[test]
fn test_xref_free_list_long() {
let mut w = Pdf::new();
w.set_version(1, 4);
w.indirect(Ref::new(1)).primitive(1);
w.indirect(Ref::new(2)).primitive(2);
w.indirect(Ref::new(5)).primitive(5);
test!(
w.finish(),
b"%PDF-1.4\n%\x80\x80\x80\x80\n",
b"1 0 obj\n1\nendobj\n",
b"2 0 obj\n2\nendobj\n",
b"5 0 obj\n5\nendobj\n",
b"xref",
b"0 6",
b"0000000003 65535 f\r",
b"0000000016 00000 n\r",
b"0000000034 00000 n\r",
b"0000000004 00000 f\r",
b"0000000000 00000 f\r",
b"0000000052 00000 n\r",
b"trailer",
b"<<\n /Size 6\n>>",
b"startxref\n70\n%%EOF",
)
}
#[test]
#[should_panic(expected = "duplicate indirect reference id: 3")]
fn test_xref_free_list_duplicate() {
let mut w = Pdf::new();
w.indirect(Ref::new(3)).primitive(1);
w.indirect(Ref::new(5)).primitive(2);
w.indirect(Ref::new(13)).primitive(1);
w.indirect(Ref::new(3)).primitive(1);
w.indirect(Ref::new(6)).primitive(2);
w.finish();
}
#[test]
fn test_binary_marker() {
let mut w = Pdf::new();
w.set_binary_marker(b"ABCD");
test!(
w.finish(),
b"%PDF-1.7\n%ABCD\n",
b"xref\n0 1\n0000000000 65535 f\r",
b"trailer\n<<\n /Size 1\n>>",
b"startxref\n16\n%%EOF",
);
}
}