use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PdfName(String);
impl PdfName {
pub fn new<S: Into<String>>(name: S) -> Self {
PdfName(name.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
pub fn encode(&self) -> String {
let mut result = String::with_capacity(self.0.len() + 1);
result.push('/');
for byte in self.0.bytes() {
if !(0x21..=0x7E).contains(&byte)
|| byte == b'#'
|| byte == b'/'
|| byte == b'%'
|| byte == b'('
|| byte == b')'
|| byte == b'<'
|| byte == b'>'
|| byte == b'['
|| byte == b']'
|| byte == b'{'
|| byte == b'}'
{
result.push('#');
result.push_str(&format!("{:02X}", byte));
} else {
result.push(byte as char);
}
}
result
}
pub const TYPE: PdfName = PdfName(String::new()); }
impl PdfName {
pub fn type_() -> Self {
PdfName::new("Type")
}
pub fn subtype() -> Self {
PdfName::new("Subtype")
}
pub fn page() -> Self {
PdfName::new("Page")
}
pub fn pages() -> Self {
PdfName::new("Pages")
}
pub fn catalog() -> Self {
PdfName::new("Catalog")
}
pub fn font() -> Self {
PdfName::new("Font")
}
pub fn resources() -> Self {
PdfName::new("Resources")
}
pub fn contents() -> Self {
PdfName::new("Contents")
}
pub fn media_box() -> Self {
PdfName::new("MediaBox")
}
pub fn parent() -> Self {
PdfName::new("Parent")
}
pub fn kids() -> Self {
PdfName::new("Kids")
}
pub fn count() -> Self {
PdfName::new("Count")
}
pub fn length() -> Self {
PdfName::new("Length")
}
pub fn filter() -> Self {
PdfName::new("Filter")
}
pub fn flate_decode() -> Self {
PdfName::new("FlateDecode")
}
}
impl fmt::Display for PdfName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.encode())
}
}
impl From<&str> for PdfName {
fn from(s: &str) -> Self {
PdfName::new(s)
}
}
impl From<String> for PdfName {
fn from(s: String) -> Self {
PdfName::new(s)
}
}
impl AsRef<str> for PdfName {
fn as_ref(&self) -> &str {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_name_creation() {
let name = PdfName::new("Type");
assert_eq!(name.as_str(), "Type");
}
#[test]
fn test_name_display() {
let name = PdfName::new("Type");
assert_eq!(format!("{}", name), "/Type");
}
#[test]
fn test_name_encoding() {
let name = PdfName::new("Type");
assert_eq!(name.encode(), "/Type");
let name_with_space = PdfName::new("Name With Space");
assert!(name_with_space.encode().contains("#20"));
}
#[test]
fn test_common_names() {
assert_eq!(PdfName::type_().as_str(), "Type");
assert_eq!(PdfName::page().as_str(), "Page");
assert_eq!(PdfName::catalog().as_str(), "Catalog");
}
#[test]
fn test_whitespace_encoding() {
let name = PdfName::new("a\0b\tc\nd\x0Ce\rf g");
let encoded = name.encode();
assert!(encoded.contains("#00"), "NULL should be encoded");
assert!(encoded.contains("#09"), "TAB should be encoded");
assert!(encoded.contains("#0A"), "LF should be encoded");
assert!(encoded.contains("#0C"), "FF should be encoded");
assert!(encoded.contains("#0D"), "CR should be encoded");
assert!(encoded.contains("#20"), "SPACE should be encoded");
assert!(!encoded.contains('\t'), "TAB should not appear raw");
assert!(!encoded.contains('\n'), "LF should not appear raw");
assert!(
!encoded.contains(' '),
"SPACE should not appear raw (except in #20)"
);
}
}