kaj / rust-pdf

Generating PDF files in pure Rust
144 stars 13 forks source link

Dump to any type that implements Write + Seek traits #9

Open mariopal opened 7 months ago

mariopal commented 7 months ago

Instead of always writing the generated PDF to a File, I have modified the code a little so that it dumps the result to any type W that implements Write + Seek:

diff --git a/src/fontsource.rs b/src/fontsource.rs
index 5c7f38c..77f2f67 100644
--- a/src/fontsource.rs
+++ b/src/fontsource.rs
@@ -5,7 +5,7 @@ use crate::fontmetrics::{get_builtin_metrics, FontMetrics};
 use crate::Pdf;
 use std::cmp::Eq;
 use std::hash::Hash;
-use std::io::{self, Write};
+use std::io::{self, Write, Seek};

 /// The "Base14" built-in fonts in PDF.
 /// Underscores in these names are hyphens in the real names.
@@ -38,7 +38,7 @@ pub trait FontSource: PartialEq + Eq + Hash {
     ///
     /// This is called automatically for each font used in a document.
     /// There should be no need to call this method from user code.
-    fn write_object(&self, pdf: &mut Pdf) -> io::Result<usize>;
+    fn write_object<W: Write + Seek>(&self, pdf: &mut Pdf<W>) -> io::Result<usize>;

     /// Get the PDF name of this font.
     ///
@@ -80,7 +80,8 @@ pub trait FontSource: PartialEq + Eq + Hash {
 }

 impl FontSource for BuiltinFont {
-    fn write_object(&self, pdf: &mut Pdf) -> io::Result<usize> {
+    fn write_object<W: Write + Seek>(&self, pdf: &mut Pdf<W>) -> io::Result<usize>
+    {
         // Note: This is enough for a Base14 font, other fonts will
         // require a stream for the actual font, and probably another
         // object for metrics etc
diff --git a/src/lib.rs b/src/lib.rs
index 6dd4812..950cc9e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -85,8 +85,8 @@ pub use crate::textobject::TextObject;
 /// are appended with the `render_page` method.
 /// Don't forget to call `finish` when done, to write the document
 /// trailer, without it the written file won't be a proper PDF.
-pub struct Pdf {
-    output: File,
+pub struct Pdf<W> {
+    output: W,
     object_offsets: Vec<i64>,
     page_objects_ids: Vec<usize>,
     all_font_object_ids: HashMap<BuiltinFont, usize>,
@@ -97,15 +97,17 @@ pub struct Pdf {
 const ROOT_OBJECT_ID: usize = 1;
 const PAGES_OBJECT_ID: usize = 2;

-impl Pdf {
+impl Pdf<File> {
     /// Create a new PDF document as a new file with given filename.
-    pub fn create(filename: &str) -> io::Result<Pdf> {
+    pub fn create(filename: &str) -> io::Result<Pdf<File>> {
         let file = File::create(filename)?;
         Pdf::new(file)
     }
+}

+impl<W: Write + Seek> Pdf<W> {
     /// Create a new PDF document, writing to `output`.
-    pub fn new(mut output: File) -> io::Result<Pdf> {
+    pub fn new(mut output: W) -> io::Result<Pdf<W>> {
         // TODO Maybe use a lower version?  Possibly decide by features used?
         output.write_all(b"%PDF-1.7\n%\xB5\xED\xAE\xFB\n")?;
         Ok(Pdf {
@@ -255,7 +257,7 @@ impl Pdf {

     fn write_new_object<F, T>(&mut self, write_content: F) -> io::Result<T>
     where
-        F: FnOnce(usize, &mut Pdf) -> io::Result<T>,
+        F: FnOnce(usize, &mut Pdf<W>) -> io::Result<T>,
     {
         let id = self.object_offsets.len();
         let (result, offset) =
@@ -270,7 +272,7 @@ impl Pdf {
         write_content: F,
     ) -> io::Result<T>
     where
-        F: FnOnce(&mut Pdf) -> io::Result<T>,
+        F: FnOnce(&mut Pdf<W>) -> io::Result<T>,
     {
         assert!(self.object_offsets[id] == -1);
         let (result, offset) = self.write_object(id, write_content)?;
@@ -284,7 +286,7 @@ impl Pdf {
         write_content: F,
     ) -> io::Result<(T, i64)>
     where
-        F: FnOnce(&mut Pdf) -> io::Result<T>,
+        F: FnOnce(&mut Pdf<W>) -> io::Result<T>,
     {
         // `as i64` here would overflow for PDF files bigger than 2**63 bytes
         let offset = self.tell()? as i64;

I only put it here in case you might be interested in adding it to the current code. I think it is compatible with the code that already uses this library. I use it to save the PDF in memory and return it directly as a response to a web request, without going through the file system.

kaj commented 7 months ago

Thank! This looks good. I'll test it properly when I have a little more time, hopefully within a week or few. If you want to be "in the git log", you can submit the changes as a PR (otherwise I'll probably just commit something similar myself and attribute it to you in text).

mariopal commented 7 months ago

Ok, you can make the commit yourself and if you want, mention me in the text. I also provide a code example to use the new Pdf::new() function, which in addition to the File type, accepts other types that implement Write + Seek traits, such as std::io::Cursor:

//! Example program dumping a PDF to a memory buffer and then to a file
use std::io::Cursor;
use pdf_canvas::{Pdf, BuiltinFont};
use pdf_canvas::graphicsstate::Color;

fn main() {
//  let mut document = Pdf::create("example.pdf").expect("Create pdf file");
  let mut buf: Vec<u8> = Vec::new();
  let fakefile = Cursor::new(&mut buf); //Create fake "file" from buf
  let mut document = Pdf::new(fakefile).expect("Create pdf buffer");

  let font = BuiltinFont::Helvetica;
  let head = "Header";
  let foot = "Footer";

  // Add a page to the document.  This page will be 594 by 841 pt large.
  document.render_page(594.0, 841.0, |canvas| {
    canvas.set_fill_color(Color::rgb(0, 0, 0))?;
    canvas.left_text(80.0  + 320.0*0.0, 761.0 - 530.0*0.0 , font, 9.0, head)?;
    canvas.rectangle(80.0  + 320.0*0.0, 755.0 - 530.0*0.0, 108.0, 4.0)?;
    canvas.rectangle(184.0 + 320.0*0.0, 647.0 - 530.0*0.0, 4.0, 108.0)?;
    canvas.rectangle(80.0  + 320.0*0.0, 647.0 - 530.0*0.0, 108.0, 4.0)?;
    canvas.rectangle(80.0  + 320.0*0.0, 647.0 - 530.0*0.0, 4.0, 108.0)?;
    canvas.left_text(80.0  + 320.0*0.0, 637.0 - 530.0*0.0 , font, 9.0, foot)?;
    canvas.fill()?;

    canvas.left_text(80.0  + 320.0*1.0, 761.0 - 530.0*1.0 , font, 9.0, head)?;
    canvas.rectangle(80.0  + 320.0*1.0, 755.0 - 530.0*1.0, 108.0, 4.0)?;
    canvas.rectangle(184.0 + 320.0*1.0, 647.0 - 530.0*1.0, 4.0, 108.0)?;
    canvas.rectangle(80.0  + 320.0*1.0, 647.0 - 530.0*1.0, 108.0, 4.0)?;
    canvas.rectangle(80.0  + 320.0*1.0, 647.0 - 530.0*1.0, 4.0, 108.0)?;
    canvas.left_text(80.0  + 320.0*1.0, 637.0 - 530.0*1.0 , font, 9.0, foot)?;
    canvas.fill()?;

    Ok(())
  }).expect("Write page");
  // Write all pending content, including the trailer and index
  document.finish().expect("Finish pdf document");

  //Dump the buf in memory to a PDF file
  std::fs::write("example.pdf", buf.as_slice()).expect("Create pdf file");
}//main