printpdf 0.12.4

Rust library for reading and writing PDF files
Documentation
use std::{
    io::Read,
    sync::atomic::{AtomicUsize, Ordering},
};

use crate::date::OffsetDateTime;

/// Since the random number generator doesn't have to be cryptographically secure
/// it doesn't make sense to import the entire rand library, so this is just a
/// xorshift pseudo-random function
static RAND_SEED: AtomicUsize = AtomicUsize::new(2100);

/// Xorshift-based random number generator. Impure function
pub(crate) fn random_number() -> usize {
    let mut x = RAND_SEED.fetch_add(21, Ordering::SeqCst);
    #[cfg(target_pointer_width = "64")]
    {
        x ^= x << 21;
        x ^= x >> 35;
        x ^= x << 4;
        x
    }

    #[cfg(target_pointer_width = "32")]
    {
        x ^= x << 13;
        x ^= x >> 17;
        x ^= x << 5;
        x
    }
}

/// Returns a string with 32 random characters
pub(crate) fn random_character_string_32() -> String {
    const MAX_CHARS: usize = 32;
    let mut final_string = String::with_capacity(MAX_CHARS);
    let mut char_pos = 0;

    'outer: while char_pos < MAX_CHARS {
        let rand = format!("{}", crate::utils::random_number());
        for ch in rand.chars() {
            if char_pos < MAX_CHARS {
                final_string.push(u8_to_char(ch.to_digit(10).unwrap() as u8));
                char_pos += 1;
            } else {
                break 'outer;
            }
        }
    }

    final_string
}

// D:20170505150224+02'00'
#[cfg(target_family = "wasm")]
pub(crate) fn to_pdf_time_stamp_metadata(_date: &OffsetDateTime) -> String {
    "D:19700101000000+00'00'".to_string()
}

#[cfg(not(target_family = "wasm"))]
pub(crate) fn to_pdf_time_stamp_metadata(date: &OffsetDateTime) -> String {
    format!(
        "D:{:04}{:02}{:02}{:02}{:02}{:02}+00'00'",
        date.year(),
        u8::from(date.month()),
        date.day(),
        date.hour(),
        date.minute(),
        date.second(),
    )
}

#[cfg(target_family = "wasm")]
pub(crate) fn to_pdf_xmp_date(_date: &OffsetDateTime) -> String {
    "D:1970-01-01T00:00:00+00'00'".to_string()
}

// D:2018-09-19T10:05:05+00'00'
#[cfg(not(target_family = "wasm"))]
pub(crate) fn to_pdf_xmp_date(date: &OffsetDateTime) -> String {
    // Since the time is in UTC, we know that the time zone
    // difference to UTC is 0 min, 0 sec, hence the 00'00
    format!(
        "D:{:04}-{:02}-{:02}T{:02}:{:02}:{:02}+00'00'",
        date.year(),
        // u8, not the Month enum: Month's Display ignores the {:02} width, so months
        // 1–9 rendered single-digit — an XMP date veraPDF rejects, defeating the
        // PDF/A / PDF/X conformance the metadata exists for.
        u8::from(date.month()),
        date.day(),
        date.hour(),
        date.minute(),
        date.second(),
    )
}

/// `0 => A`, `1 => B`, and so on
#[inline(always)]
fn u8_to_char(input: u8) -> char {
    (b'A' + input) as char
}

#[allow(dead_code)]
pub(crate) fn compress(bytes: &[u8]) -> Vec<u8> {
    use std::io::prelude::*;

    use flate2::{write::GzEncoder, Compression};
    let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
    let _ = encoder.write_all(bytes);
    encoder.finish().unwrap_or_default()
}

pub(crate) fn uncompress(bytes: &[u8]) -> Vec<u8> {
    use flate2::read::GzDecoder;
    let mut gz = GzDecoder::new(bytes);
    let mut s = Vec::<u8>::new();
    let _ = gz.read_to_end(&mut s);
    s
}

#[cfg(test)]
mod tests {
    #[cfg(not(target_family = "wasm"))]
    #[test]
    fn xmp_date_zero_pads_single_digit_month() {
        use crate::date::{Date, DateTime, Offset, Time};

        let d = DateTime::new_in_offset(
            Date {
                year: 2018,
                month: 9,
                day: 3,
            },
            Time {
                hour: 1,
                minute: 2,
                second: 3,
                millisecond: 0,
            },
            Offset {
                hours: 0,
                minutes: 0,
                seconds: 0,
                milliseconds: 0,
            },
        );
        assert_eq!(super::to_pdf_xmp_date(&d), "D:2018-09-03T01:02:03+00'00'");
    }
}