Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enum of operations #109

Open
z33ky opened this issue Jul 31, 2020 · 3 comments
Open

enum of operations #109

z33ky opened this issue Jul 31, 2020 · 3 comments

Comments

@z33ky
Copy link
Contributor

z33ky commented Jul 31, 2020

In my project I've introduced an enum to give me a more readable representation of the PDF operations:

enum PdfOperation {
    OpenPath { x: PdfNumber, y: PdfNumber },
    Line { x: PdfNumber, y: PdfNumber },
    ClosePath {},
    Stroke {},
    ClosePathAndStroke {},
    SetStrokeGrayLevel { gray: Percentage },
    SetStrokeRGBColor { r: Percentage, g: Percentage, b: Percentage },
    SetLineWidth { width: NonNegativePdfNumber },
    BeginText {},
    EndText {},
    NextLine {},
    MoveTextCursor { x: PdfNumber, y: PdfNumber },
    SetTextFont { font: String, size: PdfNumber },
    SetTextLeading { leading: PdfNumber },
    ShowText { text: PdfString },
    NextLineAndShowText { text: PdfString },
}

impl Into<lopdf::content::Operation> for PdfOperation {
    fn into(self) -> lopdf::content::Operation {
        use lopdf::content::Operation;
        macro_rules! operation {
            ($operator:expr $(,)?) => { Operation::new($operator, vec![]) };
            ($operator:expr, $($operants:expr),+ $(,)?) => { Operation::new($operator, vec![$($operants.into()),+]) };
        }
        match self {
            Self::OpenPath { x, y }             => operation!("m", x, y),
            Self::ClosePath {}                  => operation!("h"),
            Self::Line { x, y }                 => operation!("l", x, y),
            Self::Stroke {}                     => operation!("S"),
            Self::ClosePathAndStroke {}         => operation!("s"),
            Self::SetStrokeGrayLevel { gray }   => operation!("G", gray),
            Self::SetStrokeRGBColor { r, g, b } => operation!("RG", r, g, b),
            Self::SetLineWidth { width }        => operation!("w", width),
            Self::BeginText {}                  => operation!("BT"),
            Self::EndText {}                    => operation!("ET"),
            Self::NextLine {}                   => operation!("T*"),
            Self::MoveTextCursor { x, y }       => operation!("Td", x, y),
            Self::SetTextFont { font, size }    => operation!("Tf", font, size),
            Self::SetTextLeading { leading }    => operation!("TL", leading),
            Self::ShowText { text }             => operation!("Tj", text),
            Self::NextLineAndShowText { text }  => operation!("'", text),
        }
    }
}

The PdfNumber and PdfString types are subsets of Object:

#[derive(Debug, Clone, Copy)]
enum PdfNumber {
    Integer(i64),
    Real(f64),
}

impl Into<lopdf::Object> for PdfNumber {
    fn into(self) -> lopdf::Object {
        use lopdf::Object;
        match self {
            Self::Integer(i) => Object::Integer(i),
            Self::Real(r)    => Object::Real(r),
        }
    }
}

impl From<i64> for PdfNumber {
    fn from(i: i64) -> Self {
        Self::Integer(i)
    }
}

impl From<f64> for PdfNumber {
    fn from(r: f64) -> Self {
        Self::Real(r)
    }
}

#[derive(Debug, Clone)]
enum PdfString {
    Literal(Vec<u8>),
    Hexadecimal(Vec<u8>),
}

impl Into<lopdf::Object> for PdfString {
    fn into(self) -> lopdf::Object {
        use lopdf::{Object, StringFormat};
        match self {
            Self::Literal(s)     => Object::String(s, StringFormat::Literal),
            Self::Hexadecimal(s) => Object::String(s, StringFormat::Hexadecimal),
        }
    }
}

impl From<String> for PdfString {
    fn from(s: String) -> Self {
        Self::from(s.as_str())
    }
}

impl<'a> From<&'a str> for PdfString {
    fn from(s: &'a str) -> Self {
        // probably specific to my usecase
        use encoding::Encoding as _;
        Self::Literal(encoding::all::ISO_8859_1.encode(s, encoding::EncoderTrap::Strict)
            .unwrap_or_else(|e| panic!("Encoding failire: {}", e)))
    }
}

Percentage and NonNegativePdfNumber are wrappers like std::num::NonZero*, to ensure valid ranges:

#[derive(Debug, Clone, Copy)]
// strictly in range [0..1]
struct Percentage(f64);

impl Percentage {
    pub fn new(r: f64) -> Self {
        assert!(r >= 0.);
        assert!(r <= 1.);
        Self(r)
    }
}

impl Into<lopdf::Object> for Percentage {
    fn into(self) -> lopdf::Object {
        lopdf::Object::Real(self.0)
    }
}

#[derive(Debug, Clone, Copy)]
enum NonNegativePdfNumber {
    Integer(i64),
    Real(f64),
}

impl NonNegativePdfNumber {
    pub fn new_integer(i: i64) -> Self {
        assert!(i >= 0);
        Self::Integer(i)
    }

    pub fn new_real(r: f64) -> Self {
        assert!(r >= 0.);
        Self::Real(r)
    }
}

impl Into<lopdf::Object> for NonNegativePdfNumber {
    fn into(self) -> lopdf::Object {
        use lopdf::Object;
        match self {
            Self::Integer(i) => Object::Integer(i),
            Self::Real(r)    => Object::Real(r),
        }
    }
}

Obviously this is only the subset that I require. Maybe extending it and integrating it into lopdf would be desirable though.
Here's an example usage:

macro_rules! operations_vec {
    ($($operation:expr),* $(,)?) => { vec![$($operation.into()),*] };
}

let content = lopdf::content::Content {
    operations: operations_vec![
        PdfOperation::BeginText {},
        PdfOperation::SetTextFont { font: "F1".into(), size: 48.into() },
        PdfOperation::MoveTextCursor { x: 100.into(), y: 600.into() },
        PdfOperation::ShowText { text: "Hello World!".into() },
        PdfOperation::EndText {},
    ],
};
@Boltzmachine
Copy link

I also wonder why this project intensively uses strings instead of enums? Is there any reason?

@J-F-Liu
Copy link
Owner

J-F-Liu commented Apr 30, 2024

Just because defining so many enums is very laborious.

@Heinenen
Copy link
Collaborator

Heinenen commented Aug 3, 2024

Would you be open to a potential PR that implements operations as enum instead of strings?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants