Xournal PDF embedding wrapper
Thursday, 28 January 2010 20:58

Xournal is a great notebook application to be used on Tablet PCs. It allows for annotation of PDF documents, with the drawing/writing exported directly to the PDF. While there is vector-drawing on top, the underlying PDF is not changed (making use of PDFs updating mechanism), giving rise to a rather high quality result.

Sometimes it turns out to be inconvenient that one ends up with three files — the original PDF, the Xournal file, and the annotated PDF. Because the annotations are just stored as an update, the original PDF can always be recovered by removing the most recent update. Also, PDF allows for embedding files within the document. So in principle, a single file can be sufficient: it appears to be the fully annotated version as exported by Xournal to any PDF viewer. Additionally, the Xournal file is included which allows for editing those annotations after removing the Xournal's PDF attachments.

By making a little script out of this, annotating a PDF (and also editing an already annotated one) is as easy as

xournalpdf document.pdf

and only a single file has to be kept.

The script makes use of PDF::API2 along with PDF:API2::Names. By giving it a non-existent file name, an empty Xournal file is generated in contrast to PDF annotation. Passing an existent Xournal filename will start Xournal and embed the source in the resulting PDF.

What might be most interesting is the code to append some file attachment to a PDF: The Root-object is cloned and the name-tree of embedded files extended by an entry describing the Xournal file and also containing its encoded content.

  1. sub append( $$$ )
  2. {
  3. my ( $pdfname, $filename, $description ) = ( shift, shift, shift );
  4. my $spdf = PDF::API2::Basic::PDF::File->open( $pdfname, 1 );
  5. $description = $filename unless $description;
  6.  
  7. $spdf->{Root}->realise;
  8. $spdf->{Root} = $spdf->new_obj( $spdf->{Root} );
  9.  
  10. if ( defined $spdf->{Root}->{Names} )
  11. {
  12. $spdf->{Root}->{Names}->realise;
  13. $spdf->{Root}->{Names} = $spdf->new_obj( $spdf->{Root}->{Names} );
  14. }
  15. else
  16. { $spdf->{Root}->{Names} = PDF::API2::Basic::PDF::Dict->new; }
  17. $spdf->{Root}->{Names}->{' parent'} = $spdf;
  18. bless $spdf->{Root}->{Names}, 'PDF::API2::Names';
  19.  
  20. my $file = PDF::API2::Basic::PDF::Dict->new;
  21. @$file{'Type', ' streamfile', 'Filter'} = (
  22. PDFName( 'EmbeddedFile' ),
  23. $filename,
  24. PDFArray( PDFName( 'FlateDecode' ) )
  25. );
  26.  
  27. my $descr = PDF::API2::Basic::PDF::Dict->new;
  28. @$descr{qw/Type F EF/} = (
  29. PDFName( 'Filespec' ),
  30. PDFStr( $filename ),
  31. PDF::API2::Basic::PDF::Dict->new
  32. );
  33. $descr->{EF}->{F} = $file;
  34.  
  35. $spdf->{Root}->{Names}->set( 'EmbeddedFiles', $description, $descr );
  36. $spdf->new_obj( $descr );
  37. $spdf->new_obj( $file );
  38. $spdf->append_file;
  39. $spdf->release;
  40. }

Download: xournalpdf