mod array;
mod dict;
mod name;
mod number;
mod reference;
mod stream;
mod string;
pub use array::PdfArray;
pub use dict::PdfDict;
pub use name::PdfName;
pub use number::PdfNumber;
pub use reference::PdfRef;
pub use stream::PdfStream;
pub use string::{PdfHexString, PdfString};
pub fn format_real(value: f64) -> String {
if value.is_nan() {
return "0".to_string();
}
if value.is_infinite() {
return if value.is_sign_positive() {
"999999999".to_string()
} else {
"-999999999".to_string()
};
}
if value.fract() == 0.0 && value.abs() < i64::MAX as f64 {
return format!("{}", value as i64);
}
if value == 0.0 || (value.abs() > 1e-6 && value.abs() < 1e12) {
let mut buffer = ryu::Buffer::new();
let formatted = buffer.format(value);
if formatted.contains('e') || formatted.contains('E') {
let s = format!("{:.10}", value);
s.trim_end_matches('0').trim_end_matches('.').to_string()
} else if formatted.contains('.') {
formatted
.trim_end_matches('0')
.trim_end_matches('.')
.to_string()
} else {
formatted.to_string()
}
} else {
let s = format!("{:.6}", value);
s.trim_end_matches('0').trim_end_matches('.').to_string()
}
}
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub enum PdfObject {
Null,
Bool(bool),
Integer(i64),
Real(f64),
Name(PdfName),
String(PdfString),
HexString(PdfHexString),
Array(PdfArray),
Dict(PdfDict),
Stream(PdfStream),
Reference(PdfRef),
}
impl PdfObject {
pub fn is_null(&self) -> bool {
matches!(self, PdfObject::Null)
}
pub fn as_bool(&self) -> Option<bool> {
match self {
PdfObject::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_integer(&self) -> Option<i64> {
match self {
PdfObject::Integer(i) => Some(*i),
_ => None,
}
}
pub fn as_real(&self) -> Option<f64> {
match self {
PdfObject::Real(r) => Some(*r),
PdfObject::Integer(i) => Some(*i as f64),
_ => None,
}
}
pub fn as_name(&self) -> Option<&PdfName> {
match self {
PdfObject::Name(n) => Some(n),
_ => None,
}
}
pub fn as_string(&self) -> Option<&PdfString> {
match self {
PdfObject::String(s) => Some(s),
_ => None,
}
}
pub fn as_array(&self) -> Option<&PdfArray> {
match self {
PdfObject::Array(a) => Some(a),
_ => None,
}
}
pub fn as_array_mut(&mut self) -> Option<&mut PdfArray> {
match self {
PdfObject::Array(a) => Some(a),
_ => None,
}
}
pub fn as_dict(&self) -> Option<&PdfDict> {
match self {
PdfObject::Dict(d) => Some(d),
_ => None,
}
}
pub fn as_dict_mut(&mut self) -> Option<&mut PdfDict> {
match self {
PdfObject::Dict(d) => Some(d),
_ => None,
}
}
pub fn as_stream(&self) -> Option<&PdfStream> {
match self {
PdfObject::Stream(s) => Some(s),
_ => None,
}
}
pub fn as_reference(&self) -> Option<PdfRef> {
match self {
PdfObject::Reference(r) => Some(*r),
_ => None,
}
}
pub fn type_name(&self) -> &'static str {
match self {
PdfObject::Null => "null",
PdfObject::Bool(_) => "boolean",
PdfObject::Integer(_) => "integer",
PdfObject::Real(_) => "real",
PdfObject::Name(_) => "name",
PdfObject::String(_) => "string",
PdfObject::HexString(_) => "hexstring",
PdfObject::Array(_) => "array",
PdfObject::Dict(_) => "dictionary",
PdfObject::Stream(_) => "stream",
PdfObject::Reference(_) => "reference",
}
}
}
impl fmt::Display for PdfObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PdfObject::Null => write!(f, "null"),
PdfObject::Bool(b) => write!(f, "{}", if *b { "true" } else { "false" }),
PdfObject::Integer(i) => write!(f, "{}", i),
PdfObject::Real(r) => {
if r.fract() == 0.0 {
write!(f, "{:.1}", r)
} else {
write!(f, "{}", r)
}
}
PdfObject::Name(n) => write!(f, "{}", n),
PdfObject::String(s) => write!(f, "{}", s),
PdfObject::HexString(s) => write!(f, "{}", s),
PdfObject::Array(a) => write!(f, "{}", a),
PdfObject::Dict(d) => write!(f, "{}", d),
PdfObject::Stream(s) => write!(f, "{}", s),
PdfObject::Reference(r) => write!(f, "{}", r),
}
}
}
impl From<bool> for PdfObject {
fn from(b: bool) -> Self {
PdfObject::Bool(b)
}
}
impl From<i32> for PdfObject {
fn from(i: i32) -> Self {
PdfObject::Integer(i as i64)
}
}
impl From<i64> for PdfObject {
fn from(i: i64) -> Self {
PdfObject::Integer(i)
}
}
impl From<f64> for PdfObject {
fn from(r: f64) -> Self {
PdfObject::Real(r)
}
}
impl From<f32> for PdfObject {
fn from(r: f32) -> Self {
PdfObject::Real(r as f64)
}
}
impl From<PdfName> for PdfObject {
fn from(n: PdfName) -> Self {
PdfObject::Name(n)
}
}
impl From<PdfString> for PdfObject {
fn from(s: PdfString) -> Self {
PdfObject::String(s)
}
}
impl From<PdfHexString> for PdfObject {
fn from(s: PdfHexString) -> Self {
PdfObject::HexString(s)
}
}
impl From<PdfArray> for PdfObject {
fn from(a: PdfArray) -> Self {
PdfObject::Array(a)
}
}
impl From<PdfDict> for PdfObject {
fn from(d: PdfDict) -> Self {
PdfObject::Dict(d)
}
}
impl From<PdfStream> for PdfObject {
fn from(s: PdfStream) -> Self {
PdfObject::Stream(s)
}
}
impl From<PdfRef> for PdfObject {
fn from(r: PdfRef) -> Self {
PdfObject::Reference(r)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pdf_object_types() {
assert_eq!(PdfObject::Null.type_name(), "null");
assert_eq!(PdfObject::Bool(true).type_name(), "boolean");
assert_eq!(PdfObject::Integer(42).type_name(), "integer");
assert_eq!(PdfObject::Real(2.73).type_name(), "real");
}
#[test]
fn test_pdf_object_display() {
assert_eq!(format!("{}", PdfObject::Null), "null");
assert_eq!(format!("{}", PdfObject::Bool(true)), "true");
assert_eq!(format!("{}", PdfObject::Bool(false)), "false");
assert_eq!(format!("{}", PdfObject::Integer(42)), "42");
}
#[test]
fn test_format_real_integers() {
assert_eq!(format_real(0.0), "0");
assert_eq!(format_real(1.0), "1");
assert_eq!(format_real(-1.0), "-1");
assert_eq!(format_real(42.0), "42");
assert_eq!(format_real(1000000.0), "1000000");
}
#[test]
fn test_format_real_decimals() {
assert_eq!(format_real(0.5), "0.5");
assert_eq!(format_real(2.73), "2.73");
assert_eq!(format_real(-2.5), "-2.5");
let result = format_real(1.50);
assert!(!result.ends_with('0') || result == "0", "Got: {}", result);
}
#[test]
fn test_format_real_precision() {
let value = 0.123456789;
let formatted = format_real(value);
let parsed: f64 = formatted.parse().unwrap();
assert_eq!(
value, parsed,
"Value should round-trip: {} -> {}",
value, formatted
);
}
#[test]
fn test_format_real_special_values() {
assert_eq!(format_real(f64::NAN), "0");
assert_eq!(format_real(f64::INFINITY), "999999999");
assert_eq!(format_real(f64::NEG_INFINITY), "-999999999");
}
#[test]
fn test_format_real_small_values() {
let result = format_real(0.001);
assert!(
result.contains("0.001") || result == "0.001",
"Got: {}",
result
);
}
#[test]
fn test_format_real_no_scientific_notation() {
let test_values = [1.1e-6, 1.5e-6, 2.0e-6, 9.9e-6, 1e-6, 1e-7, 1e-10];
for &v in &test_values {
let result = format_real(v);
assert!(
!result.contains('e') && !result.contains('E'),
"Scientific notation in PDF real: format_real({}) = {:?}",
v,
result,
);
}
}
}