Skip to content

Commit

Permalink
add html to pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
0xfourzerofour committed Jul 12, 2022
1 parent 0b4f105 commit deaa1f7
Show file tree
Hide file tree
Showing 10 changed files with 1,497 additions and 38 deletions.
881 changes: 844 additions & 37 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "rusty_pdf"
description = "Crate adding text and images to existing pdf files"
version = "0.2.0"
version = "0.21.0"
authors = ["Joshua Pauline <[email protected]>"]
edition = "2021"
license = "MIT OR Apache-2.0"
Expand All @@ -19,3 +19,5 @@ lopdf = { version = "0.27.0", features = [
], default-features = false, git = "https://github.com/J-F-Liu/lopdf", branch = "master" }
png = "0.17.2"
imagesize = "0.9"
headless_chrome = "0.9.0"
tiny_http = "0.6"
439 changes: 439 additions & 0 deletions examples/data/test.html

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions examples/html.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use rusty_pdf::PDFSigningDocument;

fn main() {
let merged_doc = PDFSigningDocument::generate_pdf_from_html(include_str!("data/test.html"));

merged_doc.finished().save("generated_html.pdf").unwrap();
}
3 changes: 3 additions & 0 deletions examples/test.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background-color: #000000;
}
35 changes: 35 additions & 0 deletions examples/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,42 @@ use std::fs;

use rusty_pdf::{Font, PDFSigningDocument};

#[derive(Debug)]
enum EventType {
Sent,
Completed,
Signed,
}

#[derive(Debug)]
struct EventUser<'a> {
email: &'a str,
ip: &'a str,
audit_id: &'a str,
}

#[derive(Debug)]
struct TableRow<'a> {
event: EventType,
time: &'a str,
user: EventUser<'a>,
}

fn main() {
let rows = vec![TableRow {
event: EventType::Signed,
time: "1234",
user: EventUser {
email: "[email protected]",
ip: "00.00.00.00",
audit_id: "12345qwert",
},
}];

for i in rows {
print!("{:?}", i);
}

let doc_mem = fs::read("examples/data/pdf_example.pdf").unwrap_or(vec![]);

let doc = Document::load_mem(&doc_mem).unwrap_or_default();
Expand Down
Binary file added generated_html.pdf
Binary file not shown.
Binary file added new_pdf_with_data.pdf
Binary file not shown.
42 changes: 42 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
mod error;
mod image_xobject;
mod pdf_object;
mod utils;

use headless_chrome::{Browser, LaunchOptionsBuilder, Tab};
use image_xobject::ImageXObject;
use lopdf::{content::Operation, dictionary, Bookmark, Document, Object, ObjectId};
use pdf_object::PdfObjectDeref;
use std::{
collections::{BTreeMap, HashMap},
io::Read,
sync::Arc,
};

pub use error::Error;
pub use lopdf;
use utils::Server;

#[derive(Debug, Clone, Default)]
pub struct Rectangle {
Expand Down Expand Up @@ -52,6 +56,30 @@ pub struct PDFSigningDocument {
// font_unsafe_name: HashMap<String, String>,
}

fn browser() -> Browser {
Browser::new(
LaunchOptionsBuilder::default()
.headless(true)
.build()
.unwrap(),
)
.unwrap()
}

fn dumb_client(server: &Server) -> (Browser, Arc<Tab>) {
let browser = browser();
let tab = browser.wait_for_initial_tab().unwrap();
tab.navigate_to(&format!("http://127.0.0.1:{}", server.port()))
.unwrap();
(browser, tab)
}

fn dumb_server(data: &'static str) -> (Server, Browser, Arc<Tab>) {
let server = Server::with_dumb_html(data);
let (browser, tab) = dumb_client(&server);
(server, browser, tab)
}

impl PDFSigningDocument {
pub fn new(raw_document: Document) -> Self {
PDFSigningDocument {
Expand All @@ -60,6 +88,20 @@ impl PDFSigningDocument {
}
}

pub fn generate_pdf_from_html(html_content: &'static str) -> Self {
let (_server, _browser, tab) = dumb_server(&html_content);

let local_pdf = tab
.wait_until_navigated()
.unwrap()
.print_to_pdf(None)
.unwrap();

let new_pdf = Document::load_mem(&local_pdf).unwrap();

return PDFSigningDocument::new(new_pdf);
}

pub fn merge(documents: Vec<Document>) -> Result<Self, Error> {
let mut max_id = 1;
let mut pagenum = 1;
Expand Down
124 changes: 124 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
use std::sync::{atomic, Arc};
use std::thread::JoinHandle;
use std::time::Duration;
use std::{fs, io};

pub struct Server {
server: Arc<tiny_http::Server>,
handler: Option<JoinHandle<Result<(), io::Error>>>,
shall_exit: Arc<atomic::AtomicBool>,
}

impl Server {
pub fn new(
mut responder: impl FnMut(tiny_http::Request) -> Result<(), io::Error> + Send + 'static,
) -> Self {
let server = Arc::new(tiny_http::Server::http("127.0.0.1:0").unwrap());
let shall_exit = Arc::new(atomic::AtomicBool::new(false));
let srv = server.clone();
let exit = shall_exit.clone();
let handler = std::thread::spawn(move || {
loop {
if let Some(r) = srv.recv_timeout(Duration::from_millis(1000))? {
responder(r)?;
}
if exit.load(atomic::Ordering::Relaxed) {
break;
}
}
Ok(())
});
Server {
server,
handler: Some(handler),
shall_exit,
}
}

#[allow(dead_code)]
pub fn with_dumb_html(data: &'static str) -> Self {
let responder = move |r: tiny_http::Request| {
let response = tiny_http::Response::new(
200.into(),
vec![
tiny_http::Header::from_bytes(&b"Content-Type"[..], &b"text/html"[..]).unwrap(),
],
io::Cursor::new(data),
Some(data.len()),
None,
);
r.respond(response)
};
Self::new(responder)
}

#[allow(dead_code)]
pub fn url(&self) -> String {
format!("http://127.0.0.1:{}", self.port())
}

pub fn port(&self) -> u16 {
self.server.server_addr().port()
}

pub fn exit(&mut self) -> Result<(), io::Error> {
self.shall_exit.store(true, atomic::Ordering::Relaxed);
match self.handler.take() {
Some(h) => h.join().unwrap(),
None => Ok(()),
}
}
}

impl Drop for Server {
fn drop(&mut self) {
self.exit().unwrap()
}
}

fn basic_http_response<'a>(
body: &'a str,
content_type: &'static str,
) -> tiny_http::Response<&'a [u8]> {
tiny_http::Response::new(
200.into(),
vec![tiny_http::Header::from_bytes(&b"Content-Type"[..], content_type.as_bytes()).unwrap()],
body.as_bytes(),
Some(body.len()),
None,
)
}

#[allow(dead_code)]
fn not_found_response() -> tiny_http::Response<io::Empty> {
tiny_http::Response::new_empty(404.into())
}

#[allow(dead_code)]
pub fn file_server(path: &'static str) -> Server {
Server::new(move |request: tiny_http::Request| {
let url = if request.url() == "/" {
"/index.html"
} else {
request.url()
};

let file_path = format!("{}{}", path, url);

if let Ok(file_contents) = fs::read_to_string(file_path) {
let content_type = if url.ends_with(".js") {
"application/javascript"
} else if url.ends_with(".css") {
"text/css"
} else {
"text/html"
};

let response = basic_http_response(&file_contents, content_type);
request.respond(response)
} else {
let response = not_found_response();
request.respond(response)
}
})
}

0 comments on commit deaa1f7

Please sign in to comment.