3 # Format the documentation as PostScript
6 require 'psfonts.ph'; # The fonts we want to use
7 require 'pswidth.ph'; # PostScript string width
12 # PostScript configurables; these values are also available to the
13 # PostScript code itself
16 pagewidth => 595, # Page width in PostScript points
17 pageheight => 792, # Page height in PostScript points
18 lmarg => 100, # Left margin in PostScript points
19 rmarg => 50, # Right margin in PostScript points
20 topmarg => 100, # Top margin in PostScript points
21 botmarg => 100, # Bottom margin in PostScript points
22 plmarg => 50, # Page number position relative to left margin
23 prmarg => 0, # Page number position relative to right margin
24 pymarg => 50, # Page number position relative to bot margin
25 bulladj => 12, # How much to indent a bullet paragraph
26 tocind => 12, # TOC indentation per level
27 tocpnz => 24, # Width of TOC page number only zone
28 tocdots => 8, # Spacing between TOC dots
29 idxspace => 24, # Minimum space between index title and pg#
30 idxindent => 32, # How much to indent a subindex entry
31 idxgutter => 24, # Space between index columns
32 idxcolumns => 2, # Number of index columns
36 colorlinks => 0, # Set links in blue rather than black
41 'a4' => [595, 842], # ISO standard paper size
42 'letter' => [612, 792], # US common paper size
43 'pa4' => [595, 792], # Compromise ("portable a4")
44 'b4' => [709,1002], # ISO intermediate paper size
45 'legal' => [612,1008], # US intermediate paper size
46 'a3' => [842,1190], # ISO double paper size
47 '11x17' => [792,1224], # US double paper size
51 # Parse the command line
54 while ( $arg = shift(@ARGV) ) {
55 if ( $arg =~ /^\-(|no\-)/ ) {
57 $true = ($1 eq '') ? 1 : 0;
58 if ( $true && defined($papersizes{$parm}) ) {
59 $psconf{pagewidth} = $papersizes{$parm}->[0];
60 $psconf{pageheight} = $papersizes{$parm}->[1];
61 } elsif ( defined($psbool{$parm}) ) {
62 $psbool{$parm} = $true;
63 } elsif ( $true && defined($psconf{$parm}) ) {
64 $psconf{$parm} = shift(@ARGV);
66 die "$0: Unknown option: $arg\n";
74 # Document formatting parameters
76 $paraskip = 6; # Space between paragraphs
77 $chapstart = 30; # Space before a chapter heading
78 $chapskip = 24; # Space after a chapter heading
79 $tocskip = 6; # Space between TOC entries
81 # Configure post-paragraph skips for each kind of paragraph
82 %skiparray = ('chap' => $chapskip, 'appn' => $chapstart,
83 'head' => $paraskip, 'subh' => $paraskip,
84 'norm' => $paraskip, 'bull' => $paraskip,
85 'code' => $paraskip, 'toc0' => $tocskip,
86 'toc1' => $tocskip, 'toc2' => $tocskip);
89 # First, format the stuff coming from the front end into
90 # a cleaner representation
92 if ( defined($input) ) {
93 sysopen(PARAS, $input, O_RDONLY) or
94 die "$0: cannot open $input: $!\n";
96 open(PARAS, "<&STDIN") or die "$0: $!\n";
98 while ( defined($line = <PARAS>) ) {
102 if ( $line =~ /^meta :/ ) {
104 $metadata{$metakey} = $data;
105 } elsif ( $line =~ /^indx :/ ) {
107 push(@ixentries, $ixentry);
108 $ixterms{$ixentry} = [split(/\037/, $data)];
109 # Look for commas. This is easier done on the string
110 # representation, so do it now.
111 if ( $line =~ /^(.*\,)\037sp\037/ ) {
113 $ixhasprefix{$ixentry} = $ixprefix;
114 if ( !$ixprefixes{$ixprefix} ) {
115 $ixcommafirst{$ixentry}++;
117 $ixprefixes{$ixprefix}++;
120 push(@ptypes, $line);
121 push(@paras, [split(/\037/, $data)]);
127 # Convert an integer to a chosen base
133 my($z) = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
134 return '0' if ($i == 0);
135 if ( $i < 0 ) { $n = '-'; $i = -$i; }
137 $s = substr($z,$i%$b,1) . $s;
144 # Take a crossreference name and generate the PostScript name for it.
146 # This hack produces a somewhat smaller PDF...
151 # my $q = $ps_xref_list{$s};
152 # return $q if ( defined($ps_xref_list{$s}) );
153 # $q = 'X'.int2base($ps_xref_next++, 52);
154 # $ps_xref_list{$s} = $q;
158 # Somewhat bigger PDF, but one which obeys # URLs
164 # Flow lines according to a particular font set and width
166 # A "font set" is represented as an array containing
167 # arrays of pairs: [<size>, <metricref>]
169 # Each line is represented as:
170 # [ [type,first|last,aux,fontset,page,ypos,optional col],
171 # [rendering array] ]
173 # A space character may be "squeezed" by up to this much
174 # (as a fraction of the normal width of a space.)
176 $ps_space_squeeze = 0.00; # Min space width 100%
177 sub ps_flow_lines($$$@) {
178 my($wid, $fontset, $type, @data) = @_;
179 my($fonts) = $$fontset{fonts};
181 my($w) = 0; # Width of current line
182 my($sw) = 0; # Width of current line due to spaces
183 my(@l) = (); # Current line
184 my(@ls) = (); # Accumulated output lines
185 my(@xd) = (); # Metadata that goes with subsequent text
186 my $hasmarker = 0; # Line has -6 marker
187 my $pastmarker = 0; # -6 marker found
189 # If there is a -6 marker anywhere in the paragraph,
190 # *each line* output needs to have a -6 marker
191 foreach $e ( @data ) {
192 $hasmarker = 1 if ( $$e[0] == -6 );
196 foreach $e ( @data ) {
198 # Type is metadata. Zero width.
199 if ( $$e[0] == -6 ) {
202 if ( $$e[0] == -1 || $$e[0] == -6 ) {
203 # -1 (end anchor) or -6 (marker) goes with the preceeding
204 # text, otherwise with the subsequent text
210 my $ew = ps_width($$e[1], $fontset->{fonts}->[$$e[0]][1]) *
211 ($fontset->{fonts}->[$$e[0]][0]/1000);
213 $sp =~ tr/[^ ]//d; # Delete nonspaces
214 my $esw = ps_width($sp, $fontset->{fonts}->[$$e[0]][1]) *
215 ($fontset->{fonts}->[$$e[0]][0]/1000);
217 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
219 # Search backwards for previous space chunk
220 my $lx = scalar(@l)-1;
223 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) {
225 $pastmarker = 0 if ( $l[$lx]->[0] == -6 );
229 if ( $l[$lx]->[1] eq ' ' ) {
231 @rm = splice(@l, $lx);
232 last; # Found place to break
239 # Now @l contains the stuff to remain on the old line
240 # If we broke the line inside a link, then split the link
243 foreach my $lc ( @l ) {
244 if ( $$lc[0] == -2 || $$lc[0] == -3 || $lc[0] == -7 ) {
246 } elsif ( $$lc[0] == -1 ) {
251 if ( defined($lkref) ) {
252 push(@l, [-1,undef]); # Terminate old reference
253 unshift(@rm, $lkref); # Duplicate reference on new line
258 unshift(@rm,[-6,undef]); # New line starts with marker
260 push(@l,[-6,undef]); # Old line ends with marker
264 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
268 # Compute the width of the remainder array
270 if ( $$le[0] >= 0 ) {
271 my $xew = ps_width($$le[1], $fontset->{fonts}->[$$le[0]][1]) *
272 ($fontset->{fonts}->[$$le[0]][0]/1000);
274 $xsp =~ tr/[^ ]//d; # Delete nonspaces
275 my $xsw = ps_width($xsp, $fontset->{fonts}->[$$le[0]][1]) *
276 ($fontset->{fonts}->[$$le[0]][0]/1000);
277 $w += $xew; $sw += $xsw;
281 push(@l, @xd); # Accumulated metadata
283 if ( $$e[1] ne '' ) {
285 $w += $ew; $sw += $esw;
291 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
294 # Mark the first line as first and the last line as last
296 $ls[0]->[0]->[1] |= 1; # First in para
297 $ls[-1]->[0]->[1] |= 2; # Last in para
303 # Once we have broken things into lines, having multiple chunks
304 # with the same font index is no longer meaningful. Merge
305 # adjacent chunks to keep down the size of the whole file.
307 sub ps_merge_chunks(@) {
314 $eco = -1; # Index of the last entry in @co
316 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
317 $co[$eco]->[1] .= $$c[1];
319 push(@co, $c); $eco++;
327 # Convert paragraphs to rendering arrays. Each
328 # element in the array contains (font, string),
329 # where font can be one of:
333 # -4 index item anchor
335 # -6 left/right marker (used in the index)
336 # -7 page link (used in the index)
339 # 2 code (fixed spacing)
342 sub mkparaarray($@) {
343 my($ptype, @chunks) = @_;
349 if ( $ptype =~ /^code/ ) {
350 foreach $chunk ( @chunks ) {
351 push(@para, [2, $chunk]);
354 foreach $chunk ( @chunks ) {
355 my $type = substr($chunk,0,2);
356 my $text = substr($chunk,2);
358 if ( $type eq 'sp' ) {
359 push(@para, [$in_e?1:0, ' ']);
360 } elsif ( $type eq 'da' ) {
361 # \261 is en dash in Adobe StandardEncoding
362 push(@para, [$in_e?1:0, "\261"]);
363 } elsif ( $type eq 'n ' ) {
364 push(@para, [0, $text]);
366 } elsif ( $type =~ '^e' ) {
367 push(@para, [1, $text]);
368 $in_e = ($type eq 'es' || $type eq 'e ');
369 } elsif ( $type eq 'c ' ) {
370 push(@para, [2, $text]);
372 } elsif ( $type eq 'x ' ) {
373 push(@para, [-2, ps_xref($text)]);
374 } elsif ( $type eq 'xe' ) {
375 push(@para, [-1, undef]);
376 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
377 $text =~ /\<(.*)\>(.*)$/;
378 my $link = $1; $text = $2;
379 push(@para, [-3, $link]);
380 push(@para, [($type eq 'wc') ? 2:0, $text]);
381 push(@para, [-1, undef]);
383 } elsif ( $type eq 'i ' ) {
384 push(@para, [-4, $text]);
386 die "Unexpected paragraph chunk: $chunk";
393 $npara = scalar(@paras);
394 for ( $i = 0 ; $i < $npara ; $i++ ) {
395 $paras[$i] = [mkparaarray($ptypes[$i], @{$paras[$i]})];
399 # This converts a rendering array to a simple string
401 sub ps_arraytostr(@) {
405 $s .= $$c[1] if ( $$c[0] >= 0 );
411 # This generates a duplicate of a paragraph
426 # Scan for header paragraphs and fix up their contents;
427 # also generate table of contents and PDF bookmarks.
429 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
430 @tocptypes = ('chap');
431 @bookmarks = (['title', 0, 'Title Page'], ['contents', 0, 'Contents']);
433 for ( $i = 0 ; $i < $npara ; $i++ ) {
434 my $xtype = $ptypes[$i];
435 my $ptype = substr($xtype,0,4);
439 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
440 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
445 my $xref = ps_xref($sech);
446 my $chap = ($ptype eq 'chap')?'Chapter':'Appendix';
448 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
449 push(@bookmarks, $book);
450 $bookref{$secn} = $book;
452 push(@tocparas, [ps_dup_para(@{$paras[$i]})]);
453 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
455 unshift(@{$paras[$i]},
456 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
457 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
458 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
463 my $xref = ps_xref($sech);
465 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
467 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
468 push(@bookmarks, $book);
469 $bookref{$secn} = $book;
470 $bookref{$pref}->[1]--; # Adjust count for parent node
472 push(@tocparas, [ps_dup_para(@{$paras[$i]})]);
474 (($ptype eq 'subh') ? 'toc2':'toc1').' :'.$sech.':'.$secn);
476 unshift(@{$paras[$i]}, [-5, $xref]);
481 # Add TOC to beginning of paragraph list
483 unshift(@paras, @tocparas); undef @tocparas;
484 unshift(@ptypes, @tocptypes); undef @tocptypes;
485 $npara = scalar(@paras);
488 # No lines generated, yet.
493 # Line Auxilliary Information Types
495 $AuxStr = 1; # String
496 $AuxPage = 2; # Page number (from xref)
497 $AuxPageStr = 3; # Page number as a PostScript string
498 $AuxXRef = 4; # Cross reference as a name
499 $AuxNum = 5; # Number
502 # Break or convert paragraphs into lines, and push them
503 # onto the @pslines array.
505 sub ps_break_lines($$) {
506 my ($paras,$ptypes) = @_;
508 my $linewidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
509 my $bullwidth = $linewidth-$psconf{bulladj};
510 my $indxwidth = ($linewidth-$psconf{idxgutter})/$psconf{idxcolumns}
513 my $npara = scalar(@{$paras});
516 for ( $i = 0 ; $i < $npara ; $i++ ) {
517 my $xtype = $ptypes->[$i];
518 my $ptype = substr($xtype,0,4);
519 my @data = @{$paras->[$i]};
521 if ( $ptype eq 'code' ) {
523 # Code paragraph; each chunk is a line
524 foreach $p ( @data ) {
525 push(@ls, [[$ptype,0,undef,\%TextFont,0,0],[$p]]);
527 $ls[0]->[0]->[1] |= 1; # First in para
528 $ls[-1]->[0]->[1] |= 2; # Last in para
529 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
530 # Chapters are flowed normally, but in an unusual font
531 @ls = ps_flow_lines($linewidth, \%ChapFont, $ptype, @data);
532 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
533 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
538 my $font = ($ptype eq 'head') ? \%HeadFont : \%SubhFont;
539 @ls = ps_flow_lines($linewidth, $font, $ptype, @data);
540 # We need the heading number as auxillary data
541 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
542 } elsif ( $ptype eq 'norm' ) {
543 @ls = ps_flow_lines($linewidth, \%TextFont, $ptype, @data);
544 } elsif ( $ptype eq 'bull' ) {
545 @ls = ps_flow_lines($bullwidth, \%TextFont, $ptype, @data);
546 } elsif ( $ptype =~ /^toc/ ) {
547 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
551 my $refname = $2.' ';
552 my $ntoc = substr($ptype,3,1)+0;
553 my $refwidth = ps_width($refname, $TextFont{fonts}->[0][1]) *
554 ($TextFont{fonts}->[0][0]/1000);
556 @ls = ps_flow_lines($linewidth-$ntoc*$psconf{tocind}-
557 $psconf{tocpnz}-$refwidth,
558 \%TextFont, $ptype, @data);
560 # Auxilliary data: for the first line, the cross reference symbol
561 # and the reference name; for all lines but the first, the
562 # reference width; and for the last line, the page number
564 my $nl = scalar(@ls);
565 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
566 for ( $j = 1 ; $j < $nl ; $j++ ) {
567 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
569 push(@{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
570 } elsif ( $ptype =~ /^idx/ ) {
571 my $lvl = substr($ptype,3,1)+0;
573 @ls = ps_flow_lines($indxwidth-$lvl*$psconf{idxindent},
574 \%TextFont, $ptype, @data);
576 die "Unknown para type: $ptype";
578 # Merge adjacent identical chunks
580 @{$$l[1]} = ps_merge_chunks(@{$$l[1]});
586 # Break the main body text into lines.
587 ps_break_lines(\@paras, \@ptypes);
590 # Break lines in to pages
593 $curpage = 3; # First text page is page 3
594 $curypos = 0; # Space used on this page
595 undef $columnstart; # Not outputting columnar text
596 undef $curcolumn; # Current column
597 $nlines = scalar(@pslines);
600 # This formats lines inside the global @pslines array into pages,
601 # updating the page and y-coordinate entries. Start at the
602 # $startline position in @pslines and go to but not including
603 # $endline. The global variables $curpage, $curypos, $columnstart
604 # and $curcolumn are updated appropriately.
606 sub ps_break_pages($$) {
607 my($startline, $endline) = @_;
609 # Paragraph types which should never be broken
610 my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
611 # Paragraph types which are heading (meaning they should not be broken
613 my $headingregexp = "^(chap|appn|head|subh)\$";
614 # Paragraph types which are set in columnar format
615 my $columnregexp = "^idx.\$";
617 my $upageheight = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg};
621 for ( $i = $startline ; $i < $endline ; $i++ ) {
622 my $linfo = $pslines[$i]->[0];
623 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
624 && ($$linfo[1] & 1) ) {
625 # First line of a new chapter heading. Start a new page.
627 $curpage++ if ( $curypos > 0 || defined($columnstart) );
628 $curypos = $chapstart;
629 } elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
635 if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
636 $columnstart = $curypos;
640 # Adjust position by the appropriate leading
641 $curypos += $$linfo[3]->{leading};
643 # Record the page and y-position
644 $$linfo[4] = $curpage;
645 $$linfo[5] = $curypos;
646 $$linfo[6] = $curcolumn if ( defined($columnstart) );
648 if ( $curypos > $upageheight ) {
649 # We need to break the page before this line.
650 my $broken = 0; # No place found yet
651 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
652 my $linfo = $pslines[$i]->[0];
653 my $pinfo = $pslines[$i-1]->[0];
655 if ( $$linfo[1] == 2 ) {
656 # This would be an orphan, don't break.
657 } elsif ( $$linfo[1] & 1 ) {
658 # Sole line or start of paragraph. Break unless
659 # the previous line was part of a heading.
660 $broken = 1 if ( $$pinfo[0] !~ /$headingregexp/o );
662 # Middle of paragraph. Break unless we're in a
663 # no-break paragraph, or the previous line would
664 # end up being a widow.
665 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
670 die "Nowhere to break page $curpage\n" if ( !$broken );
671 # Now $i should point to line immediately before the break, i.e.
672 # the next paragraph should be the first on the new page
673 if ( defined($columnstart) &&
674 ++$curcolumn < $psconf{idxcolumns} ) {
675 # We're actually breaking text into columns, not pages
676 $curypos = $columnstart;
685 # Add end of paragraph skip
686 if ( $$linfo[1] & 2 ) {
687 $curypos += $skiparray{$$linfo[0]};
692 ps_break_pages(0,$nlines); # Break the main text body into pages
695 # Find the page number of all the indices
697 %ps_xref_page = (); # Crossref anchor pages
698 %ps_index_pages = (); # Index item pages
699 $nlines = scalar(@pslines);
700 for ( $i = 0 ; $i < $nlines ; $i++ ) {
701 my $linfo = $pslines[$i]->[0];
702 foreach my $c ( @{$pslines[$i]->[1]} ) {
703 if ( $$c[0] == -4 ) {
704 if ( !defined($ps_index_pages{$$c[1]}) ) {
705 $ps_index_pages{$$c[1]} = [];
706 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
707 # Pages are emitted in order; if this is a duplicated
708 # entry it will be the last one
711 push(@{$ps_index_pages{$$c[1]}}, $$linfo[4]);
712 } elsif ( $$c[0] == -5 ) {
713 $ps_xref_page{$$c[1]} = $$linfo[4];
719 # Emit index paragraphs
721 $startofindex = scalar(@pslines);
722 @ixparas = ([[-5,'index'],[0,'Index']]);
723 @ixptypes = ('chap');
725 foreach $k ( @ixentries ) {
727 my $ixptype = 'idx0';
728 my @ixpara = mkparaarray('idx0',@{$ixterms{$k}});
730 push(@ixpara, [-6,undef]); # Left/right marker
731 $i = 1; $n = scalar(@{$ps_index_pages{$k}});
732 foreach $p ( @{$ps_index_pages{$k}} ) {
734 push(@ixpara,[-7,$p],[0,"$p"],[-1,undef]);
736 push(@ixpara,[-7,$p],[0,"$p,"],[-1,undef],[0,' ']);
740 push(@ixparas, [@ixpara]);
741 push(@ixptypes, $ixptype);
745 # Flow index paragraphs into lines
747 ps_break_lines(\@ixparas, \@ixptypes);
750 # Format index into pages
752 $nlines = scalar(@pslines);
753 ps_break_pages($startofindex, $nlines);
756 # Push index onto bookmark list
758 push(@bookmarks, ['index', 0, 'Index']);
760 # Get the list of fonts used
762 foreach $fset ( @AllFonts ) {
763 foreach $font ( @{$fset->{fonts}} ) {
764 $ps_all_fonts{$font->[1]->{name}}++;
768 # Emit the PostScript DSC header
769 print "%!PS-Adobe-3.0\n";
770 print "%%Pages: $curpage\n";
771 print "%%BoundingBox: 0 0 ", $psconf{pagewidth}, ' ', $psconf{pageheight}, "\n";
772 print "%%Creator: NASM psflow.pl\n";
773 print "%%DocumentData: Clean7Bit\n";
774 print "%%DocumentFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
775 print "%%DocumentNeededFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
776 print "%%Orientation: Portrait\n";
777 print "%%PageOrder: Ascend\n";
778 print "%%EndComments\n";
779 print "%%BeginProlog\n";
781 # Emit the configurables as PostScript tokens
782 foreach $c ( keys(%psconf) ) {
783 print "/$c ", $psconf{$c}, " def\n";
785 foreach $c ( keys(%psbool) ) {
786 print "/$c ", ($psbool{$c}?'true':'false'), " def\n";
789 # Emit fontset definitions
790 foreach $fset ( @AllFonts ) {
793 foreach $font ( @{$fset->{fonts}} ) {
794 print '/', $fset->{name}, $i, ' ',
795 '/', $font->[1]->{name}, ' findfont ',
796 $font->[0], " scalefont def\n";
797 push(@zfonts, $fset->{name}.$i);
800 print '/', $fset->{name}, ' [', join(' ',@zfonts), "] def\n";
803 # Emit the result as PostScript. This is *NOT* correct code yet!
804 open(PSHEAD, "< head.ps");
805 while ( defined($line = <PSHEAD>) ) {
809 print "%%EndProlog\n";
811 # Generate a PostScript string
816 my ($l) = length($s);
817 for ( $i = 0 ; $i < $l ; $i++ ) {
818 $c = substr($s,$i,1);
819 if ( ord($c) < 32 || ord($c) > 126 ) {
820 $o .= sprintf("\\%03o", ord($c));
821 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
830 # Generate PDF bookmarks
831 print "%%BeginSetup\n";
832 foreach $b ( @bookmarks ) {
833 print '[/Title ', ps_string($b->[2]), "\n";
834 print '/Count ', $b->[1], ' ' if ( $b->[1] );
835 print '/Dest /',$b->[0]," /OUT pdfmark\n";
838 # Ask the PostScript interpreter for the proper size media
839 print "setpagesize\n";
840 print "%%EndSetup\n";
842 # Start a PostScript page
843 sub ps_start_page() {
845 print "%%Page: $ps_page $ps_page\n";
846 print "%%BeginPageSetup\n";
848 print "%%EndPageSetup\n";
849 print '/', $ps_page, " pa\n";
852 # End a PostScript page
856 print "($ps_page)", (($ps_page & 1) ? 'pageodd' : 'pageeven'), "\n";
858 print "restore showpage\n";
863 # Title page and inner cover
865 # FIX THIS: This shouldn't be hard-coded like this
866 $title = $metadata{'title'};
867 $title =~ s/ \- / \320 /; # \320 = em dash
868 $pstitle = ps_string($title);
870 lmarg pageheight 2 mul 3 div moveto
871 /Helvetica-Bold findfont 20 scalefont setfont
872 /title linkdest ${pstitle} show
873 lmarg pageheight 2 mul 3 div 10 sub moveto
874 0 setlinecap 3 setlinewidth
875 pagewidth lmarg sub rmarg sub 0 rlineto stroke
879 /Courier-Bold findfont sz scalefont setfont
882 [(-~~..~:\#;L .-:\#;L,.- .~:\#:;.T -~~.~:;. .~:;. )
883 ( E8+U *T +U\' *T\# .97 *L E8+\' *;T\' *;, )
884 ( D97 \`*L .97 \'*L \"T;E+:, D9 *L *L )
885 ( H7 I\# T7 I\# \"*:. H7 I\# I\# )
886 ( U: :8 *\#+ , :8 T, 79 U: :8 :8 )
887 (,\#B. .IE, \"T;E* .IE, J *+;\#:T*\" ,\#B. .IE, .IE,)] {
888 currentpoint 3 -1 roll
889 sz -0.10 mul 0 3 -1 roll ashow
890 sz 0.72 mul sub moveto
895 pagewidth 2 div 143 sub
896 pageheight 2 div 33 add
901 print "% Inner cover goes here\n";
906 foreach $line ( @pslines ) {
907 my $linfo = $line->[0];
909 if ( $$linfo[4] != $curpage ) {
912 $curpage = $$linfo[4];
917 foreach my $c ( @{$line->[1]} ) {
919 if ( $curfont != $$c[0] ) {
920 print ($curfont = $$c[0]);
922 print ps_string($$c[1]);
923 } elsif ( $$c[0] == -1 ) {
924 print '{el}'; # End link
925 } elsif ( $$c[0] == -2 ) {
926 print '{/',$$c[1],' xl}'; # xref link
927 } elsif ( $$c[0] == -3 ) {
928 print '{',ps_string($$c[1]),'wl}'; # web link
929 } elsif ( $$c[0] == -4 ) {
930 # Index anchor -- ignore
931 } elsif ( $$c[0] == -5 ) {
932 print '{/',$$c[1],' xa}'; #xref anchor
933 } elsif ( $$c[0] == -6 ) {
934 print ']['; # Start a new array
936 } elsif ( $$c[0] == -7 ) {
937 print '{/',$$c[1],' pl}'; # page link
939 die "Unknown annotation";
943 if ( defined($$linfo[2]) ) {
944 foreach my $x ( @{$$linfo[2]} ) {
945 if ( $$x[0] == $AuxStr ) {
946 print ps_string($$x[1]);
947 } elsif ( $$x[0] == $AuxPage ) {
948 print $ps_xref_page{$$x[1]},' ';
949 } elsif ( $$x[0] == $AuxPageStr ) {
950 print ps_string($ps_xref_page{$$x[1]});
951 } elsif ( $$x[0] == $AuxXRef ) {
952 print '/',ps_xref($$x[1]),' ';
953 } elsif ( $$x[0] == $AuxNum ) {
956 die "Unknown auxilliary data type";
960 print ($psconf{pageheight}-$psconf{topmarg}-$$linfo[5]);
961 print ' ', $$linfo[6] if ( defined($$linfo[6]) );
962 print ' ', $$linfo[0].$$linfo[1], "\n";