#!/usr/bin/perl -w use PDF::API2; use PDF::API2::Basic::PDF::File; use PDF::API2::Basic::PDF::Utils; my $fileid = 'Xournal source'; sub append( $$$ ); if ( scalar @ARGV == 0 ) { print STDERR "Usage:\n $0 myfile.(pdf|xoj)\n"; exit; } my $base = $ARGV[0]; my $xoj = ( $ARGV[0] =~ /\.xoj(?:\.gz)?$/ ); $base =~ s/\.xoj(?:\.gz)?$/.pdf/; map { die "$base.$_ already existent" if -e "$base.$_" } ( 'pdf', 'xoj' ); my $spdf = { }; $spdf = PDF::API2::Basic::PDF::File->open( $base, 1 ) if not $xoj and -e $base; my $updated = 0; my $olddata; my $annotated = 0; if ( $spdf->{' prev'} ) { $annotated = defined $spdf->{' prev'}->{' prev'}; print STDERR "found ".( $annotated ? 'two' : 'one' )." PDF update(s)\n"; $spdf->{Root}->realise; if ( $spdf->{Root}->{Names} ) { $spdf->{Root}->{Names}->realise; my $D = bless $spdf->{Root}->{Names}, 'PDF::API2::Names'; if ( my $f = $D->get( 'EmbeddedFiles', $fileid ) ) { print STDERR "found embedded Xournal source\n"; $f->realise; $f->{EF}->realise; $f->{EF}->{F}->realise; print STDERR "extracting Xournal source\n"; my $s = bless $f->{EF}->{F}, 'PDF::API2::Basic::PDF::Dict'; $s->read_stream; open OUT, "> $base.xoj"; $updated = 1; print OUT $s->{' stream'}; close OUT; print STDERR "stripping most recent PDF update(s), keeping Xournals annotations\n"; my $p1 = ${[sort { $a <=> $b } grep { $_ } map { $$_[0] } values %{$spdf->{' xref'}} ]}[0]; my $p2 = $annotated ? ${[sort { $a <=> $b } grep { $_ } map { $$_[0] } values %{$spdf->{' prev'}->{' xref'}} ]}[0] : 0; # if ( $annotated ) { seek $spdf->{' INFILE'}, $p2, 0; read $spdf->{' INFILE'}, $olddata, $p1 - $p2; } truncate $spdf->{' OUTFILE'}, $p2; close $spdf->{' OUTFILE'}; $spdf->release; } } } my $arg = $base . ( ( $updated or not $annotated and not $spdf->{Root} ) ? '.xoj' : '' ); my $stdsize; if ( $xoj or ( not $annotated and not $updated and not $spdf->{Root} ) ) { if ( $xoj ) { `cp $ARGV[0] $base.xoj`; } else { # use your favourite .xoj here print STDERR "writing standard Xournal file, creating new documents with given name is not yet supported by Xournal\n"; open OUT, "> $arg"; print OUT < EOF close OUT; $stdsize = -s $arg; } } print STDERR "starting Xournal\n"; print "xournal $arg\n"; `xournal $arg 2>/dev/null`; die "could not find Xournal source file '$arg'" unless -e $arg; if ( defined $stdsize and ( -s $arg == $stdsize and not -e "$base.pdf" ) ) { print STDERR "removing '$arg', apparently no changes saved\n"; unlink $arg; exit; } if ( -e $base.'.pdf' ) { print STDERR "found Xournal export\n"; `mv $base.pdf $base`; } elsif ( defined $olddata ) { print STDERR "re-attaching old Xournal annotations\n"; die 'could not find originial PDF file' unless -e $base; open OUT, ">> $base"; print OUT $olddata; close OUT; } elsif ( -e $ARGV[0] ) { print STDERR "could not find Xournal export\n"; exit; } print STDERR "attaching Xournal source\n"; append( $base, $base.'.xoj', $fileid ); unlink "$base.xoj"; sub append( $$$ ) { my ( $pdfname, $filename, $description ) = ( shift, shift, shift ); my $spdf = PDF::API2::Basic::PDF::File->open( $pdfname, 1 ); $description = $filename unless $description; $spdf->{Root}->realise; $spdf->{Root} = $spdf->new_obj( $spdf->{Root} ); if ( defined $spdf->{Root}->{Names} ) { $spdf->{Root}->{Names}->realise; $spdf->{Root}->{Names} = $spdf->new_obj( $spdf->{Root}->{Names} ); } else { $spdf->{Root}->{Names} = PDF::API2::Basic::PDF::Dict->new; } $spdf->{Root}->{Names}->{' parent'} = $spdf; bless $spdf->{Root}->{Names}, 'PDF::API2::Names'; my $file = PDF::API2::Basic::PDF::Dict->new; @$file{'Type', ' streamfile', 'Filter'} = ( PDFName( 'EmbeddedFile' ), $filename, PDFArray( PDFName( 'FlateDecode' ) ) ); my $descr = PDF::API2::Basic::PDF::Dict->new; @$descr{qw/Type F EF/} = ( PDFName( 'Filespec' ), PDFStr( $filename ), PDF::API2::Basic::PDF::Dict->new ); $descr->{EF}->{F} = $file; $spdf->{Root}->{Names}->set( 'EmbeddedFiles', $description, $descr ); $spdf->new_obj( $descr ); $spdf->new_obj( $file ); $spdf->append_file; $spdf->release; } package PDF::API2::Names; BEGIN { use strict; use vars qw(@ISA $VERSION); @ISA = qw(PDF::API2::Basic::PDF::Dict); use PDF::API2::Basic::PDF::Utils; } sub dictionary { my $self = shift; my $root = $self; my $cat = shift; my $create = shift || 0; $self->{' names'} = { } unless defined $self->{' names'} and ref $self->{' names'} eq 'HASH'; if ( defined $root and not defined $self->{' names'}->{$cat} and defined $self->{$cat} ) { $root->realise unless $root->{' realised'}; my @nodes = ( $root->{$cat} ); my %names = ( ); while ( my $node = shift @nodes ) { $node->realise unless $node->{' realised'}; if ( defined $node->{Kids} ) { $node->{Kids}->realise unless $node->{Kids}->{' realised'}; push @nodes, @{$node->{Kids}->val}; } elsif ( defined $node->{Names} ) { $node->{Names}->realise unless $node->{Names}->{' realised'}; for ( my $i = 0; $i < scalar @{$node->{Names}->val}; $i += 2 ) { $names{$node->{Names}->val->[$i]->val} = $node->{Names}->val->[$i + 1]; } } $self->{' parent'}->remove_obj( $node ); } $self->{' names'}->{$cat} = \%names if scalar keys %names > 0 or $create; } return $self->{' names'}->{$cat}; } sub remove { my $self = shift; $self->{' parent'}->remove_obj( $self ); return; } sub mergeNames { my $self = shift; my $src = shift; my $ignore = shift || 0; # my @categories = qw# Dests AP JavaScript Pages Templates IDS URLS EmbeddedFiles AlternatePresentations Renditions #; my @categories = qw# Dests #; foreach my $cat ( @categories ) { if ( defined $self->{' prefix'} ) { if ( defined $self->dictionary( $cat, 0 ) ) { foreach my $key ( keys %{$self->{' names'}->{$cat}} ) { $self->{' names'}->{$cat}->{$self->{' prefix'}.$key} = $self->{' names'}->{$cat}->{$key}; delete $self->{' names'}->{$cat}->{$key}; } } delete $self->{' prefix'}; } next if not defined $src->dictionary( $cat ); $self->dictionary( $cat, 1 ); foreach my $key ( keys %{$src->{' names'}->{$cat}} ) { die "duplicate key '$key' in '$cat' tree" if not $ignore and defined $self->{' names'}->{( defined $src->{' prefix'} ? $src->{' prefix'} : '' ).$key}; $self->{' names'}->{$cat}->{( defined $src->{' prefix'} ? $src->{' prefix'} : '' ).$key} = $src->{' names'}->{$cat}->{$key}; } } } sub set { my ( $self, $cat, $key, $value ) = ( shift, shift, shift, shift ); $self->dictionary( $cat, 1 ); $self->{' names'}->{$cat}->{$key} = $value; } sub get { my ( $self, $cat, $key ) = ( shift, shift, shift, shift ); $self->dictionary( $cat, 0 ); return ( defined $self->{' names'}->{$cat} ? $self->{' names'}->{$cat}->{$key} : undef ); } sub outobjdeep { my ( $self, @opts ) = @_; if ( $self->{' names'} ) { foreach my $key ( keys %{$self->{' names'}} ) { my @names = sort keys %{$self->{' names'}->{$key}}; if ( scalar @names == 0 ) { delete $self->{' names'}->{$key}; delete $self->{$key}; next; } my @layer = ( ); while ( my @ss = splice @names, 0, 5, ( ) ) { for ( my $i = 0; $i <= $#ss; $i += 2 ) { splice @ss, $i + 1, 0, ( $self->{' names'}->{$key}->{$ss[$i]} ); $ss[$i] = PDFStr( $ss[$i] ); } my $d = PDF::API2::Basic::PDF::Dict->new( ); @$d{'Names', 'Limits', ' parent'} = ( PDFArray( @ss ), PDFArray( PDFStr( $ss[0]->val ), PDFStr( $ss[$#ss - 1]->val ) ), $self->{' parent'} ); $self->{' parent'}->new_obj( $d ); push @layer, $d; } while ( scalar @layer > 1 ) { my @l = ( ); while ( my @kids = splice @layer, 0, 5, ( ) ) { if ( scalar @kids == 1 ) { push @l, $kids[0]; last; } my $d = PDF::API2::Basic::PDF::Dict->new( ); @$d{'Kids', 'Limits', ' parent'} = ( PDFArray( @kids ), PDFArray( PDFStr( $kids[0]->{Limits}->val->[0]->val ), PDFStr( $kids[$#kids]->{Limits}->val->[1]->val ) ), $self->{' parent'} ); $self->{' parent'}->new_obj( $d ); push @l, $d; } @layer = @l; } delete $layer[0]->{Limits}; if ( scalar keys %{$self->{' names'}->{$key}} > 0 ) { $self->{$key} = $layer[0]; } else { delete $self->{$key}; } } } foreach my $k ( qw[ names prefix ] ) { $self->{" $k"} = undef; delete( $self->{" $k"} ); } $self->SUPER::outobjdeep( @opts ); } 1;