n page A4 ->(2n+1) page A5 pdfs: success without finesse

Reece Arnott <[email protected]>
Newsgroups gmane.org.user-groups.linux.dunedin.general
Message-ID <[email protected]>
Due to no initial knowledge of Perl or PDF internals this is a bit of a 
call for help and also a bit of documentation on how I scratched my own 
itch.

I have a smallish e-book reader and reading pdfs on it that are 
formatted for A4 size screens is a pain. I got the idea that I could 
take pdfs with A4 size pages and turn them into pdfs with twice as many 
pages that were A5 size (note that the A standard is done so that this 
is true, A0 is 2xA1, A1 is 2xA2 etc. but what I've written is done in 
such a way that it will just chop the page in half without worrying 
about the actual dimensions). I've done that using a perl script and 
additional package that started from an answer in stackoverflow: 
https://stackoverflow.com/questions/5133725/split-a4-page-pdf-to-two-a5-pdf-page

The CAM::PDF perl library used has some documentation etc. here: 
http://search.cpan.org/dist/CAM-PDF/

The problem is that my solution (that works) is O(n^2) instead of O(n) 
in the number of pages:  for each page of the pdf it has to go to disk, 
load the entire pdf into memory, then cut out all but the page we're 
interested in. There are a number of issues I've solved along the way 
and I thought I'd solved this issue as well. Below is the working code 
and below that is a code snippet that I thought should work and a short 
description of it. I am hampered possibly by my lack of knowledge of 
perl and definitely by my lack of understanding of the pdf internal data 
structures.

The below code does the following:
- it assumes that there are a number of pdfs in the current folder you 
want to split and have a sub-folder called "low" in which these will be 
stored (called that for historical reasons as I put all my scripts in 
the same folder structure and historically used the scripts to take high 
quality audio and video and make it lower quality).
- The pdfs may be from many different sources with lots of the cool 
things in pdfs that break my simple script e.g bookmarks and hyperlinking.
- For this reason the first thing it does in the "for each pdf in this 
folder" loop is to run the pdf through a pdf->ps->pdf conversion scheme. 
In some cases this takes a very long time, as mentioned in the comments 
this may be due to converting text into images in weird circumstances. 
It is also worth mentioning that the output of the script breaks 
Ghostscript if you try and run it through the same process so its not 
the best script in the world. I'd prefer to have something that gave 
output that played well with others but I've reached the 'good enough' 
point given my ignorance of pdf internals.
- Also worth mentioning is that there may be a magical combination of 
switches for the a2ps and ps2pdf commands (that are currently doing the 
pdf->ps->pdf conversion) that would give the same result as my script 
but, again, I haven't needed to know much about them before now so my 
ignorance gets in the way.
- The output pdf starts as simply the first page of the initial pdf (the 
output of the pdf->ps->pdf conversion process) and ends as 2n+1 pages 
where n is the number of pages in the initial pdf. When Calibre does 
conversion it also duplicates the first page to use as a title page, in 
my case its just because I don't know how to create a blank pdf with the 
CAM::PDF toolkit.
- For each page of the initial pdf, the page in question is isolated by 
loading the entire pdf into memory and then chopping out all but the 
relevant page.
- this one page pdf is then appended to the end of the output pdf twice.
- The next to last page is rotated and cropped so that it is just the 
top half of the page (displayed sideways).
- The last page is then rotated and cropped so that it is just the 
bottom half of the page(displayed sideways).
- Optionally you can save a copy of the pdf we are building in memory at 
the end of each page process by uncommenting one or two of three lines 
which save it as a separate file each time based on the page number, or 
over the top of itself each time. On the 606 A4 page I tried as my main 
test this increased the average time of processing per page from 2 to 15 
seconds (but the time increased as you got nearer the end of the process 
so this 15 second average doesn't really say much aside from the 
increase in total time was approximately (15-2)*606 seconds).
- Finally after all pages have been processed the memory structure is saved.

(Note that on Ubuntu 14.04 I had to install packages for a2ps and 
libcam-pdf-perl for the below script to work)

------
#!/usr/bin/perl
use strict;
use warnings;
use CAM::PDF;

my @files = glob("*.pdf");
foreach my $file (@files) {
print "Working on file: $file\n";
print "Stripping any bookmarks etc. by creating new file run through 
pdf->ps->pdf conversion\n";

#Note the -1 is needed as by default a2ps prints 2 sheets per page
my $result=`a2ps "$file" -1 -o - | ps2pdf -dPDFSETTINGS=/screen - 
"low/$file"`;
# This may result in a blow out in pdf size without the /screen option 
(and takes an incredibly long time to process both parts of the above 
command even with it).
#Presumably this is due to an issue where it will "sometimes convert 
text to high-resolution bitmapped fonts rather than to embedded outline 
fonts. Currently this will always occur when the Postscript file uses 
CID-keyed or double-byte fonts, when the input file uses kshow, or in 
some cases if it uses fonts with non-standard encodings; it may occur in 
some other cases as well."
print $result . "\n";

my $pdffile = "low/".$file;
my $pdfout = "low/split-".$file;
#Didn't know how to create an empty structure
#So started by creating a new one based on the first page of the old one 
(so can use as title page)
#and appending the new pages to the end
my $pdf = CAM::PDF->new($pdffile) or die $CAM::PDF::errstr;
my $numpgs=$pdf->numPages();
$pdf->extractPages(1);

for (my $pagenum=1; $pagenum <= $numpgs;$pagenum++){
print "Splitting page $pagenum of $numpgs for file \"$file\"\n";

#extract a single page from the old pdf
my $duplicate = CAM::PDF->new($pdffile) or die $CAM::PDF::errstr;
$duplicate->extractPages($pagenum);
#add it twice to the end of the new pdf
$pdf->appendPDF($duplicate);
$pdf->appendPDF($duplicate);

#Should be able to do something with this except it does not seem to 
work as I would expect
#It seems to add a blank page to the end of the pdf and overwrite 
$currentpage+1 with a duplicate of $currentpage
#$pdf->duplicatePage($currentpage);

#Crop one to the top half of the (rotated) page

my $pagedict = $pdf->getPage($pdf->numPages()-1);
$pagedict->{Rotate} = CAM::PDF::Node->new('number', 90);

my $oldbox = $pdf->getValue($pagedict->{CropBox} || $pagedict->{MediaBox});
my @box = map {$pdf->getValue($_)} @{$oldbox};

$pagedict->{CropBox} = CAM::PDF::Node->new('array', [
    map {CAM::PDF::Node->new('number', $_)} $box[0], 
($box[3]+$box[1])/2, $box[2], $box[3]
]);

#Crop the other to Bottom half of the (rotated) page

$pagedict = $pdf->getPage($pdf->numPages());
$pagedict->{Rotate} = CAM::PDF::Node->new('number', 90);
$pagedict->{CropBox} = CAM::PDF::Node->new('array', [
    map {CAM::PDF::Node->new('number', $_)} $box[0], $box[1], $box[2], 
($box[3]+$box[1])/2
]);

#Save each time in case of an issue with the next page
#my $fname = "low/split-".$pagenum;
#$pdf->cleanoutput($fname);
#$pdf->cleanoutput($pdfout);
}
$pdf->cleanoutput($pdfout);
}

------

I thought the below would get rid of the O(n^2) issue as it uses a new 
data structure outside of the for loop that has the entire A4 pdf in it 
and creates a new one page pdf data structure where the content of the 
page is replaced by the content of the relevant page each time through 
the loop. In the case of the 606 page pdf this took the time per page 
down 100 fold (to the point where the 7 minutes to do the initial 
pdf->ps->pdf conversion dominated) but some of the pages it created were 
just wrong. It worked fine on the 10 page academic paper I had as a 
quick test but in the case of this 606 page maths textbook it had issues 
with some pages. I think it may have been where there were graphs and 
text on the page; only the graphs were shown.

#This one is to be a single page pdf with the contents of the page being 
replaced by the current page
my $singlepagepdf = CAM::PDF->new($pdffile) or die $CAM::PDF::errstr;
$singlepagepdf->extractPages(1);
#This is the data structure we choose each page from
my $dup = CAM::PDF->new($pdffile) or die $CAM::PDF::errstr;

for (my $pagenum=1; $pagenum <= $numpgs;$pagenum++){
#print "Splitting page $pagenum of $numpgs for file \"$file\"\n";

#extract a single page from the old pdf and put it in the single page 
pdf data structure
my $content=$dup->getPageContent($pagenum);
$singlepagepdf->setPageContent(1, $content);
#add it twice to the end of the new pdf
$pdf->appendPDF($singlepagepdf);
$pdf->appendPDF($singlepagepdf);


Maybe someone who has knowledge of the internals of pdfs and/or has used 
this perl library before can work out the command(s) I should be using 
but its just a little frustrating thing that I've lost enough sleep over 
that I won't be spending any more time on it myself (unless someone can 
throw me a bone): it works, I don't have a lot of large pdfs I want to 
split, and as I can just leave it going in the background all day if 
need be, its just an affront to my sense of rightness and 'computational 
aesthetics' :-)

-- 
"Believing men would act in their own interest was not cynicism, it turned out, but sheerest optimism; in reality men do not meet so high a standard."
-- Harry Potter and the Methods of Rationality (Chapter 84)
http://hpmor.com/

Reece Arnott
Dunedin
New Zealand


_______________________________________________
DunLUG mailing list
[email protected]
http://lists.ethernal.org/listinfo/dunlug
DunLUG Wiki - http://dunlug.kallisti.net.nz/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.