File Coverage

blib/lib/PostScript/Poster.pm
Criterion Covered Total %
statement 12 209 5.7
branch 0 108 0.0
condition 0 51 0.0
subroutine 4 8 50.0
pod 2 4 50.0
total 18 380 4.7


line stmt bran cond sub pod time code
1             package PostScript::Poster;
2              
3 1     1   1121 use strict;
  1         1  
  1         33  
4 1     1   6 use vars qw/$VERSION %mediatable/;
  1         2  
  1         72  
5 1     1   1024 use IO::File;
  1         10585  
  1         120  
6 1     1   981 use POSIX;
  1         8773  
  1         9  
7              
8             $VERSION = '0.02';
9              
10             =head1 NAME
11              
12             Hints - Perl extension for posterizing PostScript
13              
14             =head1 SYNOPSIS
15              
16             use PostScript::Poster;
17              
18             my $poster = new PostScript::Poster;
19              
20             $poster->posterize(-infile => 'a.ps', -outfile => 'b.ps',
21             -media => 'a4');
22              
23             =head1 DESCRIPTION
24              
25             Posterizing PostScript like external program poster. Based on source of
26             Jos T.J. van Eijdnhoven
27             poster from The Netherlands from 1999.
28              
29             =head1 THE POSTSCRIPT::POSTER CLASS
30              
31             =head2 new
32              
33             Constructor create instance of PostScript::Poster class.
34              
35             my $poster = new PostScript::Poster;
36              
37             =cut
38              
39             sub new {
40 0     0 1   my $class = shift;
41 0           my $obj = bless { }, $class;
42 0           my %par = @_;
43 0           for (keys %par) { $obj->{$_} = $par{$_}; }
  0            
44 0           return $obj;
45             }
46              
47             =head2 posterize
48              
49             Posterize -infile to -outfile according to arguments.
50              
51             Parameters:
52             -infile input file name
53             -outfile output file name
54             -insize input image size
55             -cutmargin horizontal and vertical cutmargin
56             -whitemargin horizontal and vertical additional white margin
57             -media media paper size
58             -poster output poster size
59             -scale linear scale factor for poster
60              
61             You must use at least one of -scale, -poster or -media parameters and you can't
62             use -scale and -poster simultaneously. -insize, -media and -poster parameters
63             are box parameter like 'A4', '3x3letter', '10x25cm', '200x200+10,10p' etc.
64             Margins are classic box parameters or percentual number like '200%' etc.
65              
66             Default values are -media => A4, -cutmargin => 5% and -insize readed from input
67             file.
68              
69             $poster->posterize(-infile => 'a.ps', -outfile => 'b.ps',
70             -media => 'a4');
71              
72             =cut
73              
74             sub posterize {
75 0     0 1   my $obj = shift;
76 0           my %args = @_;
77              
78 0 0         $args{-media} = 'A4' unless exists $args{-media};
79 0 0         $args{-cutmargin} = '5%' unless exists $args{-cutmargin};
80 0 0         $args{-whitemargin} = 0 unless exists $args{-whitemargin};
81              
82 0 0 0       if ($args{-scale} and $args{-poster}) {
83 0           die q!You can't specify both -scale and -poster parameters!;
84             }
85 0           my @mediasize = $obj->box_convert($args{-media});
86              
87 0 0         if ($mediasize[3] < $mediasize[2]) {
88 0           die 'Media should always be specified in portrait format';
89             }
90 0 0 0       if ($mediasize[2]-$mediasize[0] <= 10.0
91             or $mediasize[3]-$mediasize[1] <= 10.0) {
92 0           die 'Media size is ridiculous';
93             }
94              
95 0           $obj->{mediasize} = \@mediasize;
96              
97 0 0 0       $args{-poster} = $args{-media} unless $args{-scale} or $args{-poster};
98 0           my @cutmargin = $obj->margin_convert($args{-cutmargin});
99 0           my @whitemargin = $obj->margin_convert($args{-whitemargin});
100              
101 0           my $ihandle = new IO::File $args{-infile},'r';
102 0 0         return unless defined $ihandle;
103              
104 0           my $ohandle = new IO::File "> $args{-outfile}";
105 0 0         unless (defined $ohandle) { undef $ohandle; return; }
  0            
  0            
106              
107 0           my $myname = $0;
108 0           $myname =~ s/^.*\///;
109 0           print $ohandle "%!PS-Adobe-3.0\n%%Creator: $myname => PostScript::Poster $VERSION\n";
110              
111 0           my $dsc_cont = my $atend = my $inbody = my $level = 0;
112 0           my @psbb = ();
113 0           while (<$ihandle>) {
114 0 0         unless (/^%/) {
115 0 0         $dsc_cont = 0; ++$inbody unless $inbody;
  0            
116 0 0         last if $atend; next;
  0            
117             }
118 0 0 0       if (/^%%\+/ and $dsc_cont) { print $ohandle $_; next; }
  0            
  0            
119 0           $dsc_cont = 0;
120 0 0 0       if (/^%%EndComments/i) { $inbody = 1; last if $atend; }
  0 0 0        
  0 0 0        
    0 0        
    0 0        
    0          
    0          
121 0           elsif (/^%%Begin(Document|Data)/i) { ++$level; }
122 0           elsif (/^%%End(Document|Data)/i) { --$level; }
123 0           elsif (/^%%Trailer/i and not $level) { $inbody = 2; }
124             elsif (/^%%BoundingBox:/i and $inbody != 1 and not $level) {
125 0           my $readed = $_; $readed =~ s/^%%BoundingBox:\s*//i;
  0            
126 0 0         if ($readed =~ /^(atend)/i) { ++$atend; }
  0            
127 0           else { @psbb = split /\s+/,$readed; }
128             } elsif (/^%%Document/i and $inbody != 1 and not $level) {
129 0           my $readed = $_; $readed =~ s/^%%Document\S+\s*//i;
  0            
130 0 0         if ($readed =~ /^(atend)/i) { ++$atend; }
  0            
131 0           else { print $ohandle $_; ++$dsc_cont; }
  0            
132             }
133             }
134              
135 0 0 0       $args{-insize} = 'A4' unless @psbb or $args{-insize};
136 0           my @imagebb = ();
137 0 0         if ($args{-insize}) {
138 0           @imagebb = $obj->box_convert($args{-insize});
139             } else {
140 0           @imagebb = @psbb[0..3];
141             }
142              
143 0 0 0       if ($imagebb[2]-$imagebb[0] <= 0 or $imagebb[3]-$imagebb[2] <= 0) {
144 0           die "Input image should have positive size!\n";
145             }
146              
147 0           my $drawablex = $mediasize[2] - 2*$cutmargin[0];
148 0           my $drawabley = $mediasize[3] - 2*$cutmargin[1];
149              
150 0           my ($nx0,$ny0,$nx1,$ny1,$sizex,$sizey);
151              
152 0 0         if ($args{-scale}) {
153 0           my $scale = $args{-scale};
154 0 0 0       if ($scale < 0.01 or $scale > 1e6) {
155 0           die "Illegal scale value $scale!";
156             }
157 0           $sizex = ($imagebb[2]-$imagebb[0])*$scale+2*$whitemargin[0];
158 0           $sizey = ($imagebb[3]-$imagebb[1])*$scale+2*$whitemargin[1];
159              
160             # without rotation
161 0           $nx0 = POSIX::ceil($sizex / $drawablex);
162 0           $ny0 = POSIX::ceil($sizey / $drawabley);
163            
164             # with rotation
165 0           $nx1 = POSIX::ceil($sizex / $drawabley);
166 0           $ny1 = POSIX::ceil($sizey / $drawablex);
167              
168             } else {
169 0           my @tmp = $obj->box_convert($args{-poster});
170 0 0 0       if ($tmp[0] != 0 or $tmp[1] != 0) {
171 0           print STDERR "Poster lower-left coordinates are assumed 0!\n";
172 0           $tmp[0] = $tmp[1] = 0;
173             }
174 0 0 0       if ($tmp[2]-$tmp[0] <= 0 or $tmp[3]-$tmp[1] <= 0) {
175 0           die "Poster should have positive size!\n";
176             }
177 0 0         if ($tmp[3]-$tmp[1] < $tmp[2]-$tmp[0]) {
178             # hmm ... landscape ... change to portrait for now
179 0           @tmp = ($tmp[1],$tmp[0],$tmp[3],$tmp[2]);
180             }
181 0 0         if ($imagebb[3]-$imagebb[1] < $imagebb[2]-$imagebb[0]) {
182             # image ... landscape ... change to landscape
183 0           @tmp = ($tmp[1],$tmp[0],$tmp[3],$tmp[2]);
184             }
185              
186             # without rotation
187 0           $nx0 = POSIX::ceil (0.95 * $tmp[2] / $mediasize[2]);
188 0           $ny0 = POSIX::ceil (0.95 * $tmp[3] / $mediasize[3]);
189            
190             # with rotation
191 0           $nx1 = POSIX::ceil (0.95 * $tmp[2] / $mediasize[3]);
192 0           $ny1 = POSIX::ceil (0.95 * $tmp[3] / $mediasize[2]);
193             }
194              
195             # decide for rotation to get the minimum page count
196 0           my $rotate = ($nx0*$ny0 > $nx1*$ny1);
197              
198 0 0         my $ncols = ($rotate ? $nx1 : $nx0);
199 0 0         my $nrows = ($rotate ? $ny1 : $ny0);
200              
201 0 0         if ($nrows * $ncols > 400) {
202 0           die "However $nrows"."x$ncols pages seems ridiculous to me!\n";
203             }
204              
205 0 0         my $mediax = $ncols * ($rotate ? $drawabley : $drawablex);
206 0 0         my $mediay = $nrows * ($rotate ? $drawablex : $drawabley);
207              
208 0           my $scale = '';
209 0 0         unless ($args{-scale}) {
210             # no scaling number
211 0           my $scalex = ($mediax-2*$whitemargin[0])/($imagebb[2]-$imagebb[0]);
212 0           my $scaley = ($mediay-2*$whitemargin[1])/($imagebb[3]-$imagebb[1]);
213 0 0         $scale = ($scalex < $scaley) ? $scalex : $scaley;
214              
215 0           $sizex = $scale * ($imagebb[2] - $imagebb[0]);
216 0           $sizey = $scale * ($imagebb[3] - $imagebb[1]);
217             } else {
218 0           $scale = $args{-scale};
219             }
220              
221 0           my $p0 = ($mediax - $sizex)/2;
222 0           my $p1 = ($mediay - $sizey)/2;
223              
224 0           my @posterbb = ($p0,$p1,$p0+$sizex,$p1+$sizey);
225            
226 0           print $ohandle "%%Pages: ",$nrows*$ncols,"\n";
227 0           print $ohandle "%%DocumentMedia: $args{-media} ",int($mediasize[2])," ",int($mediasize[3])," 0 white ()\n";
228 0           print $ohandle "%%BoundingBox: 0 0 ",int($mediasize[2])," ",$mediasize[3],"\n";
229 0           print $ohandle "%%EndComments\n\n";
230 0           print $ohandle "% Print poster $args{-infile} in $nrows","x$ncols tiles with ",sprintf("%.3g",$scale)," magnification\n";
231              
232 0           print $ohandle "%%BeginProlog\n";
233              
234 0           printf $ohandle "/cutmark %% - cutmark -\n".
235             "{ %% draw cutline\n".
236             " 0.23 setlinewidth 0 setgray\n".
237             " clipmargin\n".
238             " dup 0 moveto\n".
239             " dup neg leftmargin add 0 rlineto stroke\n".
240             " %% draw sheet alignment mark\n".
241             " dup dup neg moveto\n".
242             " dup 0 rlineto\n".
243             " dup dup lineto\n".
244             " 0 rlineto\n".
245             " closepath fill\n".
246             "} bind def\n\n";
247              
248 0           printf $ohandle "%% usage: row col tileprolog ps-code tilepilog\n".
249             "%% these procedures output the tile specified by row & col\n".
250             "/tileprolog\n".
251             "{ %%def\n".
252             " gsave\n".
253             " leftmargin botmargin translate\n".
254             " do_turn {exch} if\n".
255             " /colcount exch def\n".
256             " /rowcount exch def\n".
257             " %% clip page contents\n".
258             " clipmargin neg dup moveto\n".
259             " pagewidth clipmargin 2 mul add 0 rlineto\n".
260             " 0 pageheight clipmargin 2 mul add rlineto\n".
261             " pagewidth clipmargin 2 mul add neg 0 rlineto\n".
262             " closepath clip\n".
263             " %% set page contents transformation\n".
264             " do_turn\n".
265             " { pagewidth 0 translate\n".
266             " 90 rotate\n".
267             " } if\n".
268             " pagewidth colcount 1 sub mul neg\n".
269             " pageheight rowcount 1 sub mul neg\n".
270             " do_turn {exch} if\n".
271             " translate\n".
272             " posterxl posteryb translate\n".
273             " sfactor dup scale\n".
274             " imagexl neg imageyb neg translate\n".
275             " tiledict begin\n".
276             " 0 setgray 0 setlinecap 1 setlinewidth\n".
277             " 0 setlinejoin 10 setmiterlimit [] 0 setdash newpath\n".
278             "} bind def\n\n";
279              
280 0           printf $ohandle "/tileepilog\n".
281             "{ end %% of tiledict\n".
282             " grestore\n".
283             " %% print the cutmarks\n".
284             " gsave\n".
285             " leftmargin botmargin translate\n".
286             " pagewidth pageheight translate cutmark 90 rotate cutmark\n".
287             " 0 pagewidth translate cutmark 90 rotate cutmark\n".
288             " 0 pageheight translate cutmark 90 rotate cutmark\n".
289             " 0 pagewidth translate cutmark 90 rotate cutmark\n".
290             " grestore\n".
291             " %% print the page label\n".
292             " 0 setgray\n".
293             " leftmargin clipmargin 3 mul add clipmargin labelsize add neg botmargin add moveto\n".
294             " (Grid \\( ) show\n".
295             " rowcount strg cvs show\n".
296             " ( , ) show\n".
297             " colcount strg cvs show\n".
298             " ( \\)) show\n".
299             " showpage\n".
300             "} bind def\n\n";
301              
302 0           print $ohandle "%%EndProlog\n\n";
303 0           print $ohandle "%%BeginSetup\n";
304 0           printf $ohandle "%% Try to inform the printer about the desired media size:\n".
305             "/setpagedevice where %% level-2 page commands available...\n".
306             "{ pop %% ignore where found\n".
307             " 3 dict dup /PageSize [ %d %d ] put\n".
308             " dup /Duplex false put\n%s".
309             " setpagedevice\n".
310             "} if\n",
311             int($mediasize[2]),int($mediasize[3]),
312             0?" dup /ManualFeed true put\n":""; # $manualfeed ?
313              
314 0 0         printf $ohandle "/sfactor %.10f def\n".
315             "/leftmargin %d def\n".
316             "/botmargin %d def\n".
317             "/pagewidth %d def\n".
318             "/pageheight %d def\n".
319             "/imagexl %d def\n".
320             "/imageyb %d def\n".
321             "/posterxl %d def\n".
322             "/posteryb %d def\n".
323             "/do_turn %s def\n".
324             "/strg 10 string def\n".
325             "/clipmargin 6 def\n".
326             "/labelsize 9 def\n".
327             "/tiledict 250 dict def\n".
328             "tiledict begin\n".
329             "%% delay users showpage until cropmark is printed.\n".
330             "/showpage {} def\n".
331             "/setpagedevice { pop } def\n".
332             "end\n",
333             $scale, int($cutmargin[0]), int($cutmargin[1]),
334             int($mediasize[2]-2*$cutmargin[0]),int($mediasize[3]-2*$cutmargin[1]),
335             int($imagebb[0]),int($imagebb[1]),int($posterbb[0]),int($posterbb[1]),
336             $rotate?"true":"false";
337              
338 0           print $ohandle "/Helvetica findfont labelsize scalefont setfont\n";
339              
340 0           print $ohandle "%%EndSetup\n";
341              
342 0           my $tail_cntl_D = 0; my $page = 1;
  0            
343 0           for my $row (1..$nrows) {
344 0           for my $col (1..$ncols) {
345 0           print $ohandle "\n%%Page: $page $page\n";
346 0           print $ohandle "$row $col tileprolog\n";
347 0           print $ohandle "%%BeginDocument: $args{-infile}\n";
348            
349 0           $ihandle->seek(0,0);
350              
351 0           my $bp = 0;
352 0           my @buf = ();
353 0           $buf[$bp] = <$ihandle>;
354              
355 0           while ($buf[1-$bp] = <$ihandle>) {
356 0 0         print $ohandle $buf[$bp] if $buf[$bp] !~ /^%/;
357 0           $bp = 1-$bp;
358             }
359 0 0         if ($buf[$bp] =~ s/\x4//) { ++$tail_cntl_D; }
  0            
360              
361 0 0 0       print $ohandle $buf[$bp] if $buf[$bp] !~ /^%/ and $buf[$bp];
362              
363 0           print $ohandle "\n%%EndDocument\n";
364 0           print $ohandle "tileepilog\n";
365              
366 0           ++$page;
367             }
368             }
369              
370 0           print $ohandle "%%EOF\n";
371              
372 0 0         printf $ohandle "%c",0x4 if $tail_cntl_D;
373              
374 0           undef $obj->{oh}; undef $obj->{ih};
  0            
375              
376             }
377              
378             %mediatable = (
379             LETTER => '612,792',
380             LEGAL => '612,1008',
381             TABLOID => '792,1224',
382             LEDGER => '792,1224',
383             EXECUTIVE => '540,720',
384             MONARCH => '279,540',
385             STATEMENT => '396,612',
386             FOLIO => '612,936',
387             QUARTO => '610,780',
388             C5 => '459,649',
389             B4 => '729,1032',
390             B5 => '516,729',
391             DL => '312,624',
392             A0 => '2380,3368',
393             A1 => '1684,2380',
394             A2 => '1190,1684',
395             A3 => '842,1190',
396             A4 => '595,842',
397             A5 => '420,595',
398             A6 => '297,421',
399             P => '1,1',
400             I => '72,72',
401             FT => '864,864',
402             MM => '2.83465,2.83465',
403             CM => '28.3465,28.3465',
404             M => '2834.65,2834.65');
405              
406             # box_convert: convert user textual box spec into numbers in ps units
407             # box = [fixed x fixed][+ fixed , fixed] unit
408             # fixed = digits [ . digits]
409             # unit = medianame | i | cm | mm | m | p
410             sub box_convert {
411 0     0 0   my $obj = shift;
412 0           my $origspec = shift;
413              
414 0           my $boxspec = uc $origspec;
415              
416 0           my @psbox = ();
417              
418 0           my $mx = 1; my $my = 1; my $ox = 0; my $oy = 0;
  0            
  0            
  0            
419              
420             # parsing fixed x fixed
421 0 0         if ($boxspec =~ s/^\s*(\d+(?:\.\d+)?)\s*[x*]\s*(\d+(?:\.\d+)?)\s*//) {
422 0           $mx = $1; $my = $2;
  0            
423             }
424              
425             # parsing +fixed,fixed
426 0 0         if ($boxspec =~ s/^+(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*//) {
427 0           $ox = $1; $oy = $2;
  0            
428             }
429              
430             # parsing media or units
431 0           my $n = 0;
432 0           my $key = '';
433 0 0         if ($mediatable{$boxspec}) {
434 0           $n = 1; $key = $boxspec;
  0            
435             } else {
436 0           for (keys %mediatable) {
437 0 0         if ($boxspec =~ /$_/) { ++$n; $key = $_; }
  0            
  0            
438             }
439             }
440              
441 0 0         die "Your box spec $boxspec is not unique! (give more chars)" if $n > 1;
442 0 0         die "I don't understand your box specification $boxspec" unless $n;
443              
444 0           my ($ux,$uy) = (1,1);
445 0 0         if ($mediatable{$key} =~ /^(.*),(.*)$/) { ($ux,$uy) = ($1,$2); }
  0            
446              
447 0           @psbox = ( $ox * $ux, $oy * $uy, $mx * $ux, $my * $uy );
448              
449 0           for (0..1) {
450 0 0 0       if ($psbox[$_] < 0 or $psbox[$_+2] < $psbox[$_]) {
451 0           die "Your specification $boxspec leads to negative values!";
452             }
453             }
454              
455             # print "Box convert: $origspec into [",join(',',@psbox),"].\n";
456              
457 0           return @psbox;
458             }
459              
460             sub margin_convert {
461 0     0 0   my $obj = shift;
462 0           my $origspec = shift;
463              
464 0           my $marginspec = uc $origspec;
465              
466 0           my @margin = ();
467              
468             # not specified
469 0 0         unless ($marginspec) {
    0          
470 0           @margin = (0,0);
471             } elsif ($marginspec =~ /^(.*)%$/) { # percent
472 0           @margin = (.01 * $1 * $obj->{mediasize}->[2],
473             .01 * $1 * $obj->{mediasize}->[3]);
474             } else { # absolute
475 0           my @marg = $obj->box_convert($marginspec);
476 0           @margin = ($marg[2],$marg[3]);
477             }
478              
479 0           for (0..1) {
480 0 0 0       if ($margin[$_] < 0 or 2*$margin[$_] >= $obj->{mediasize}->[$_+2]) {
481 0           die "Margin value $origspec out of range!";
482             }
483             }
484              
485             # print "Margin convert: $origspec into [",join(',',@margin),"].\n";
486              
487 0           return @margin;
488             }
489              
490             1;
491              
492             __END__