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 startcopyright => 100, # How much above the bottom margin is the
26 # copyright notice stuff
27 bulladj => 12, # How much to indent a bullet paragraph
28 tocind => 12, # TOC indentation per level
29 tocpnz => 24, # Width of TOC page number only zone
30 tocdots => 8, # Spacing between TOC dots
31 idxspace => 24, # Minimum space between index title and pg#
32 idxindent => 32, # How much to indent a subindex entry
33 idxgutter => 24, # Space between index columns
34 idxcolumns => 2, # Number of index columns
38 colorlinks => 0, # Set links in blue rather than black
43 'a4' => [595, 842], # ISO standard paper size
44 'letter' => [612, 792], # US common paper size
45 'pa4' => [595, 792], # Compromise ("portable a4")
46 'b4' => [709,1002], # ISO intermediate paper size
47 'legal' => [612,1008], # US intermediate paper size
48 'a3' => [842,1190], # ISO double paper size
49 '11x17' => [792,1224], # US double paper size
53 # Parse the command line
56 while ( $arg = shift(@ARGV) ) {
57 if ( $arg =~ /^\-(|no\-)(.*)$/ ) {
59 $true = ($1 eq '') ? 1 : 0;
60 if ( $true && defined($papersizes{$parm}) ) {
61 $psconf{pagewidth} = $papersizes{$parm}->[0];
62 $psconf{pageheight} = $papersizes{$parm}->[1];
63 } elsif ( defined($psbool{$parm}) ) {
64 $psbool{$parm} = $true;
65 } elsif ( $true && defined($psconf{$parm}) ) {
66 $psconf{$parm} = shift(@ARGV);
68 die "$0: Unknown option: $arg\n";
76 # Document formatting parameters
78 $paraskip = 6; # Space between paragraphs
79 $chapstart = 30; # Space before a chapter heading
80 $chapskip = 24; # Space after a chapter heading
81 $tocskip = 6; # Space between TOC entries
83 # Configure post-paragraph skips for each kind of paragraph
84 %skiparray = ('chap' => $chapskip, 'appn' => $chapstart,
85 'head' => $paraskip, 'subh' => $paraskip,
86 'norm' => $paraskip, 'bull' => $paraskip,
87 'code' => $paraskip, 'toc0' => $tocskip,
88 'toc1' => $tocskip, 'toc2' => $tocskip);
91 # Custom encoding vector. This is basically the same as
92 # ISOLatin1Encoding (a level 2 feature, so we dont want to use it),
93 # but with a few extra characters thrown in. It is basically a
94 # modified Windows 1252 codepage, minus, for now, the euro sign (\200
95 # is reserved for euro.)
100 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
101 'ampersand', 'quoteright', 'parenleft',
102 'parenright', 'asterisk', 'plus', 'comma', 'minus',
103 'period', 'slash', 'zero', 'one', 'two', 'three',
104 'four', 'five', 'six', 'seven', 'eight', 'nine',
105 'colon', 'semicolon', 'less', 'equal', 'greater',
106 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
107 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
108 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
109 'bracketleft', 'backslash', 'bracketright',
110 'asciicircum', 'underscore', 'quoteleft', 'a', 'b',
111 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
112 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
113 'w', 'x', 'y', 'z', 'braceleft', 'bar',
114 'braceright', 'asciitilde', undef,
115 undef, 'macron', 'quotesinglbase', 'florin',
116 'quotedblbase', 'ellipsis', 'dagger', 'dbldagger',
117 'circumflex', 'perthousand', 'Scaron', 'guilsinglleft',
118 'OE', 'hungarumlaut', 'Zcaron', 'caron',
119 'ogonek', 'grave', 'quotesingle', 'quotedblleft',
120 'quotedblright', 'bullet', 'endash', 'emdash',
121 'tilde', 'trademark', 'scaron', 'guilsignlright',
122 'oe', 'ring', 'zcaron', 'Ydieresis',
123 'space', 'exclamdown', 'cent', 'sterling',
124 'currency', 'yen', 'brokenbar', 'section',
125 'dieresis', 'copyright', 'ordfeminine',
126 'guillemotleft', 'logicalnot', 'hyphen',
127 'registered', 'macron', 'degree', 'plusminus',
128 'twosuperior', 'threesuperior', 'acute', 'mu',
129 'paragraph', 'periodcentered', 'cedilla',
130 'onesuperior', 'ordmasculine', 'guillemotright',
131 'onequarter', 'onehalf', 'threequarters',
132 'questiondown', 'Agrave', 'Aacute', 'Acircumflex',
133 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla',
134 'Egrave', 'Eacute', 'Ecircumflex', 'Edieresis',
135 'Igrave', 'Iacute', 'Icircumflex', 'Idieresis',
136 'Eth', 'Ntilde', 'Ograve', 'Oacute', 'Ocircumflex',
137 'Otilde', 'Odieresis', 'multiply', 'Oslash',
138 'Ugrave', 'Uacute', 'Ucircumflex', 'Udieresis',
139 'Yacute', 'Thorn', 'germandbls', 'agrave', 'aacute',
140 'acircumflex', 'atilde', 'adieresis', 'aring', 'ae',
141 'ccedilla', 'egrave', 'eacute', 'ecircumflex',
142 'edieresis', 'igrave', 'iacute', 'icircumflex',
143 'idieresis', 'eth', 'ntilde', 'ograve', 'oacute',
144 'ocircumflex', 'otilde', 'odieresis', 'divide',
145 'oslash', 'ugrave', 'uacute', 'ucircumflex',
146 'udieresis', 'yacute', 'thorn', 'ydieresis'
154 # First, format the stuff coming from the front end into
155 # a cleaner representation
157 if ( defined($input) ) {
158 sysopen(PARAS, $input, O_RDONLY) or
159 die "$0: cannot open $input: $!\n";
161 open(PARAS, "<&STDIN") or die "$0: $!\n";
163 while ( defined($line = <PARAS>) ) {
167 if ( $line =~ /^meta :(.*)$/ ) {
169 $metadata{$metakey} = $data;
170 } elsif ( $line =~ /^indx :(.*)$/ ) {
172 push(@ixentries, $ixentry);
173 $ixterms{$ixentry} = [split(/\037/, $data)];
174 # Look for commas. This is easier done on the string
175 # representation, so do it now.
176 if ( $line =~ /^(.*\,)\037sp\037/ ) {
178 $ixhasprefix{$ixentry} = $ixprefix;
179 if ( !$ixprefixes{$ixprefix} ) {
180 $ixcommafirst{$ixentry}++;
182 $ixprefixes{$ixprefix}++;
185 push(@ptypes, $line);
186 push(@paras, [split(/\037/, $data)]);
192 # Convert an integer to a chosen base
198 my($z) = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
199 return '0' if ($i == 0);
200 if ( $i < 0 ) { $n = '-'; $i = -$i; }
202 $s = substr($z,$i%$b,1) . $s;
209 # Convert a string to a rendering array
216 while ( $s =~ /^(\s+|\S+)(.*)$/ ) {
225 # Take a crossreference name and generate the PostScript name for it.
227 # This hack produces a somewhat smaller PDF...
232 # my $q = $ps_xref_list{$s};
233 # return $q if ( defined($ps_xref_list{$s}) );
234 # $q = 'X'.int2base($ps_xref_next++, 52);
235 # $ps_xref_list{$s} = $q;
239 # Somewhat bigger PDF, but one which obeys # URLs
245 # Flow lines according to a particular font set and width
247 # A "font set" is represented as an array containing
248 # arrays of pairs: [<size>, <metricref>]
250 # Each line is represented as:
251 # [ [type,first|last,aux,fontset,page,ypos,optional col],
252 # [rendering array] ]
254 # A space character may be "squeezed" by up to this much
255 # (as a fraction of the normal width of a space.)
257 $ps_space_squeeze = 0.00; # Min space width 100%
258 sub ps_flow_lines($$$@) {
259 my($wid, $fontset, $type, @data) = @_;
260 my($fonts) = $$fontset{fonts};
262 my($w) = 0; # Width of current line
263 my($sw) = 0; # Width of current line due to spaces
264 my(@l) = (); # Current line
265 my(@ls) = (); # Accumulated output lines
266 my(@xd) = (); # Metadata that goes with subsequent text
267 my $hasmarker = 0; # Line has -6 marker
268 my $pastmarker = 0; # -6 marker found
270 # If there is a -6 marker anywhere in the paragraph,
271 # *each line* output needs to have a -6 marker
272 foreach $e ( @data ) {
273 $hasmarker = 1 if ( $$e[0] == -6 );
277 foreach $e ( @data ) {
279 # Type is metadata. Zero width.
280 if ( $$e[0] == -6 ) {
283 if ( $$e[0] == -1 || $$e[0] == -6 ) {
284 # -1 (end anchor) or -6 (marker) goes with the preceeding
285 # text, otherwise with the subsequent text
291 my $ew = ps_width($$e[1], $fontset->{fonts}->[$$e[0]][1],
293 ($fontset->{fonts}->[$$e[0]][0]/1000);
295 $sp =~ tr/[^ ]//d; # Delete nonspaces
296 my $esw = ps_width($sp, $fontset->{fonts}->[$$e[0]][1],
298 ($fontset->{fonts}->[$$e[0]][0]/1000);
300 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
302 # Search backwards for previous space chunk
303 my $lx = scalar(@l)-1;
306 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) {
308 $pastmarker = 0 if ( $l[$lx]->[0] == -6 );
312 if ( $l[$lx]->[1] eq ' ' ) {
314 @rm = splice(@l, $lx);
315 last; # Found place to break
322 # Now @l contains the stuff to remain on the old line
323 # If we broke the line inside a link, then split the link
326 foreach my $lc ( @l ) {
327 if ( $$lc[0] == -2 || $$lc[0] == -3 || $lc[0] == -7 ) {
329 } elsif ( $$lc[0] == -1 ) {
334 if ( defined($lkref) ) {
335 push(@l, [-1,undef]); # Terminate old reference
336 unshift(@rm, $lkref); # Duplicate reference on new line
341 unshift(@rm,[-6,undef]); # New line starts with marker
343 push(@l,[-6,undef]); # Old line ends with marker
347 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
351 # Compute the width of the remainder array
353 if ( $$le[0] >= 0 ) {
354 my $xew = ps_width($$le[1],
355 $fontset->{fonts}->[$$le[0]][1],
357 ($fontset->{fonts}->[$$le[0]][0]/1000);
359 $xsp =~ tr/[^ ]//d; # Delete nonspaces
360 my $xsw = ps_width($xsp,
361 $fontset->{fonts}->[$$le[0]][1],
363 ($fontset->{fonts}->[$$le[0]][0]/1000);
364 $w += $xew; $sw += $xsw;
368 push(@l, @xd); # Accumulated metadata
370 if ( $$e[1] ne '' ) {
372 $w += $ew; $sw += $esw;
378 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
381 # Mark the first line as first and the last line as last
383 $ls[0]->[0]->[1] |= 1; # First in para
384 $ls[-1]->[0]->[1] |= 2; # Last in para
390 # Once we have broken things into lines, having multiple chunks
391 # with the same font index is no longer meaningful. Merge
392 # adjacent chunks to keep down the size of the whole file.
394 sub ps_merge_chunks(@) {
401 $eco = -1; # Index of the last entry in @co
403 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
404 $co[$eco]->[1] .= $$c[1];
406 push(@co, $c); $eco++;
414 # Convert paragraphs to rendering arrays. Each
415 # element in the array contains (font, string),
416 # where font can be one of:
420 # -4 index item anchor
422 # -6 left/right marker (used in the index)
423 # -7 page link (used in the index)
426 # 2 code (fixed spacing)
429 sub mkparaarray($@) {
430 my($ptype, @chunks) = @_;
436 if ( $ptype =~ /^code/ ) {
437 foreach $chunk ( @chunks ) {
438 push(@para, [2, $chunk]);
441 foreach $chunk ( @chunks ) {
442 my $type = substr($chunk,0,2);
443 my $text = substr($chunk,2);
445 if ( $type eq 'sp' ) {
446 push(@para, [$in_e?1:0, ' ']);
447 } elsif ( $type eq 'da' ) {
448 push(@para, [$in_e?1:0, $endash]);
449 } elsif ( $type eq 'n ' ) {
450 push(@para, [0, $text]);
452 } elsif ( $type =~ '^e' ) {
453 push(@para, [1, $text]);
454 $in_e = ($type eq 'es' || $type eq 'e ');
455 } elsif ( $type eq 'c ' ) {
456 push(@para, [2, $text]);
458 } elsif ( $type eq 'x ' ) {
459 push(@para, [-2, ps_xref($text)]);
460 } elsif ( $type eq 'xe' ) {
461 push(@para, [-1, undef]);
462 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
463 $text =~ /\<(.*)\>(.*)$/;
464 my $link = $1; $text = $2;
465 push(@para, [-3, $link]);
466 push(@para, [($type eq 'wc') ? 2:0, $text]);
467 push(@para, [-1, undef]);
469 } elsif ( $type eq 'i ' ) {
470 push(@para, [-4, $text]);
472 die "Unexpected paragraph chunk: $chunk";
479 $npara = scalar(@paras);
480 for ( $i = 0 ; $i < $npara ; $i++ ) {
481 $paras[$i] = [mkparaarray($ptypes[$i], @{$paras[$i]})];
485 # This converts a rendering array to a simple string
487 sub ps_arraytostr(@) {
491 $s .= $$c[1] if ( $$c[0] >= 0 );
497 # This generates a duplicate of a paragraph
512 # This generates a duplicate of a paragraph, stripping anchor
515 sub ps_dup_para_noanchor(@) {
522 push(@o, [@cc]) unless ( $cc[0] == -4 || $cc[0] == -5 );
528 # Scan for header paragraphs and fix up their contents;
529 # also generate table of contents and PDF bookmarks.
531 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
532 @tocptypes = ('chap');
533 @bookmarks = (['title', 0, 'Title Page'], ['contents', 0, 'Contents']);
535 for ( $i = 0 ; $i < $npara ; $i++ ) {
536 my $xtype = $ptypes[$i];
537 my $ptype = substr($xtype,0,4);
541 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
542 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
547 my $xref = ps_xref($sech);
548 my $chap = ($ptype eq 'chap')?'Chapter':'Appendix';
550 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
551 push(@bookmarks, $book);
552 $bookref{$secn} = $book;
554 push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
555 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
557 unshift(@{$paras[$i]},
558 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
559 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
560 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
565 my $xref = ps_xref($sech);
567 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
569 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
570 push(@bookmarks, $book);
571 $bookref{$secn} = $book;
572 $bookref{$pref}->[1]--; # Adjust count for parent node
574 push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
576 (($ptype eq 'subh') ? 'toc2':'toc1').' :'.$sech.':'.$secn);
578 unshift(@{$paras[$i]}, [-5, $xref]);
583 # Add TOC to beginning of paragraph list
585 unshift(@paras, @tocparas); undef @tocparas;
586 unshift(@ptypes, @tocptypes); undef @tocptypes;
589 # Add copyright notice to the beginning
591 unshift(@paras, [[0, "\251"], [0, ' '], [0,$metadata{'year'}],
592 [0, ' '], string2array($metadata{'author'})],
593 [[0, ' ']], [string2array($metadata{'license'})]);
594 unshift(@ptypes, 'norm', 'norm', 'norm');
596 $npara = scalar(@paras);
599 # No lines generated, yet.
604 # Line Auxilliary Information Types
606 $AuxStr = 1; # String
607 $AuxPage = 2; # Page number (from xref)
608 $AuxPageStr = 3; # Page number as a PostScript string
609 $AuxXRef = 4; # Cross reference as a name
610 $AuxNum = 5; # Number
613 # Break or convert paragraphs into lines, and push them
614 # onto the @pslines array.
616 sub ps_break_lines($$) {
617 my ($paras,$ptypes) = @_;
619 my $linewidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
620 my $bullwidth = $linewidth-$psconf{bulladj};
621 my $indxwidth = ($linewidth-$psconf{idxgutter})/$psconf{idxcolumns}
624 my $npara = scalar(@{$paras});
627 for ( $i = 0 ; $i < $npara ; $i++ ) {
628 my $xtype = $ptypes->[$i];
629 my $ptype = substr($xtype,0,4);
630 my @data = @{$paras->[$i]};
632 if ( $ptype eq 'code' ) {
634 # Code paragraph; each chunk is a line
635 foreach $p ( @data ) {
636 push(@ls, [[$ptype,0,undef,\%BodyFont,0,0],[$p]]);
638 $ls[0]->[0]->[1] |= 1; # First in para
639 $ls[-1]->[0]->[1] |= 2; # Last in para
640 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
641 # Chapters are flowed normally, but in an unusual font
642 @ls = ps_flow_lines($linewidth, \%ChapFont, $ptype, @data);
643 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
644 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
649 my $font = ($ptype eq 'head') ? \%HeadFont : \%SubhFont;
650 @ls = ps_flow_lines($linewidth, $font, $ptype, @data);
651 # We need the heading number as auxillary data
652 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
653 } elsif ( $ptype eq 'norm' ) {
654 @ls = ps_flow_lines($linewidth, \%BodyFont, $ptype, @data);
655 } elsif ( $ptype eq 'bull' ) {
656 @ls = ps_flow_lines($bullwidth, \%BodyFont, $ptype, @data);
657 } elsif ( $ptype =~ /^toc/ ) {
658 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
662 my $refname = $2.' ';
663 my $ntoc = substr($ptype,3,1)+0;
664 my $refwidth = ps_width($refname, $BodyFont{fonts}->[0][1],
666 ($BodyFont{fonts}->[0][0]/1000);
668 @ls = ps_flow_lines($linewidth-$ntoc*$psconf{tocind}-
669 $psconf{tocpnz}-$refwidth,
670 \%BodyFont, $ptype, @data);
672 # Auxilliary data: for the first line, the cross reference symbol
673 # and the reference name; for all lines but the first, the
674 # reference width; and for the last line, the page number
676 my $nl = scalar(@ls);
677 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
678 for ( $j = 1 ; $j < $nl ; $j++ ) {
679 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
681 push(@{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
682 } elsif ( $ptype =~ /^idx/ ) {
683 my $lvl = substr($ptype,3,1)+0;
685 @ls = ps_flow_lines($indxwidth-$lvl*$psconf{idxindent},
686 \%BodyFont, $ptype, @data);
688 die "Unknown para type: $ptype";
690 # Merge adjacent identical chunks
692 @{$$l[1]} = ps_merge_chunks(@{$$l[1]});
698 # Break the main body text into lines.
699 ps_break_lines(\@paras, \@ptypes);
702 # Break lines in to pages
705 # Where to start on page 2, the copyright page
706 $curpage = 2; # Start on page 2
707 $curypos = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg}-
708 $psconf{startcopyright};
709 undef $columnstart; # Not outputting columnar text
710 undef $curcolumn; # Current column
711 $nlines = scalar(@pslines);
714 # This formats lines inside the global @pslines array into pages,
715 # updating the page and y-coordinate entries. Start at the
716 # $startline position in @pslines and go to but not including
717 # $endline. The global variables $curpage, $curypos, $columnstart
718 # and $curcolumn are updated appropriately.
720 sub ps_break_pages($$) {
721 my($startline, $endline) = @_;
723 # Paragraph types which should never be broken
724 my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
725 # Paragraph types which are heading (meaning they should not be broken
727 my $headingregexp = "^(chap|appn|head|subh)\$";
728 # Paragraph types which are set in columnar format
729 my $columnregexp = "^idx.\$";
731 my $upageheight = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg};
735 for ( $i = $startline ; $i < $endline ; $i++ ) {
736 my $linfo = $pslines[$i]->[0];
737 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
738 && ($$linfo[1] & 1) ) {
739 # First line of a new chapter heading. Start a new page.
741 $curpage++ if ( $curypos > 0 || defined($columnstart) );
742 $curypos = $chapstart;
743 } elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
749 if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
750 $columnstart = $curypos;
754 # Adjust position by the appropriate leading
755 $curypos += $$linfo[3]->{leading};
757 # Record the page and y-position
758 $$linfo[4] = $curpage;
759 $$linfo[5] = $curypos;
760 $$linfo[6] = $curcolumn if ( defined($columnstart) );
762 if ( $curypos > $upageheight ) {
763 # We need to break the page before this line.
764 my $broken = 0; # No place found yet
765 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
766 my $linfo = $pslines[$i]->[0];
767 my $pinfo = $pslines[$i-1]->[0];
769 if ( $$linfo[1] == 2 ) {
770 # This would be an orphan, don't break.
771 } elsif ( $$linfo[1] & 1 ) {
772 # Sole line or start of paragraph. Break unless
773 # the previous line was part of a heading.
774 $broken = 1 if ( $$pinfo[0] !~ /$headingregexp/o );
776 # Middle of paragraph. Break unless we're in a
777 # no-break paragraph, or the previous line would
778 # end up being a widow.
779 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
784 die "Nowhere to break page $curpage\n" if ( !$broken );
785 # Now $i should point to line immediately before the break, i.e.
786 # the next paragraph should be the first on the new page
787 if ( defined($columnstart) &&
788 ++$curcolumn < $psconf{idxcolumns} ) {
789 # We're actually breaking text into columns, not pages
790 $curypos = $columnstart;
799 # Add end of paragraph skip
800 if ( $$linfo[1] & 2 ) {
801 $curypos += $skiparray{$$linfo[0]};
806 ps_break_pages(0,$nlines); # Break the main text body into pages
809 # Find the page number of all the indices
811 %ps_xref_page = (); # Crossref anchor pages
812 %ps_index_pages = (); # Index item pages
813 $nlines = scalar(@pslines);
814 for ( $i = 0 ; $i < $nlines ; $i++ ) {
815 my $linfo = $pslines[$i]->[0];
816 foreach my $c ( @{$pslines[$i]->[1]} ) {
817 if ( $$c[0] == -4 ) {
818 if ( !defined($ps_index_pages{$$c[1]}) ) {
819 $ps_index_pages{$$c[1]} = [];
820 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
821 # Pages are emitted in order; if this is a duplicated
822 # entry it will be the last one
825 push(@{$ps_index_pages{$$c[1]}}, $$linfo[4]);
826 } elsif ( $$c[0] == -5 ) {
827 $ps_xref_page{$$c[1]} = $$linfo[4];
833 # Emit index paragraphs
835 $startofindex = scalar(@pslines);
836 @ixparas = ([[-5,'index'],[0,'Index']]);
837 @ixptypes = ('chap');
839 foreach $k ( @ixentries ) {
841 my $ixptype = 'idx0';
842 my @ixpara = mkparaarray('idx0',@{$ixterms{$k}});
844 push(@ixpara, [-6,undef]); # Left/right marker
845 $i = 1; $n = scalar(@{$ps_index_pages{$k}});
846 foreach $p ( @{$ps_index_pages{$k}} ) {
848 push(@ixpara,[-7,$p],[0,"$p"],[-1,undef]);
850 push(@ixpara,[-7,$p],[0,"$p,"],[-1,undef],[0,' ']);
854 push(@ixparas, [@ixpara]);
855 push(@ixptypes, $ixptype);
859 # Flow index paragraphs into lines
861 ps_break_lines(\@ixparas, \@ixptypes);
864 # Format index into pages
866 $nlines = scalar(@pslines);
867 ps_break_pages($startofindex, $nlines);
870 # Push index onto bookmark list
872 push(@bookmarks, ['index', 0, 'Index']);
874 # Get the list of fonts used
876 foreach $fset ( @AllFonts ) {
877 foreach $font ( @{$fset->{fonts}} ) {
878 $ps_all_fonts{$font->[1]->{name}}++;
882 # Emit the PostScript DSC header
883 print "%!PS-Adobe-3.0\n";
884 print "%%Pages: $curpage\n";
885 print "%%BoundingBox: 0 0 ", $psconf{pagewidth}, ' ', $psconf{pageheight}, "\n";
886 print "%%Creator: NASM psflow.pl\n";
887 print "%%DocumentData: Clean7Bit\n";
888 print "%%DocumentFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
889 print "%%DocumentNeededFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
890 print "%%Orientation: Portrait\n";
891 print "%%PageOrder: Ascend\n";
892 print "%%EndComments\n";
893 print "%%BeginProlog\n";
895 # Emit the configurables as PostScript tokens
896 foreach $c ( keys(%psconf) ) {
897 print "/$c ", $psconf{$c}, " def\n";
899 foreach $c ( keys(%psbool) ) {
900 print "/$c ", ($psbool{$c}?'true':'false'), " def\n";
903 # Emit custom encoding vector
904 $zstr = '/NASMEncoding [ ';
905 foreach $c ( @NASMEncoding ) {
906 my $z = '/'.(defined($c)?$c:'.notdef ').' ';
907 if ( length($zstr)+length($z) > 72 ) {
913 print $zstr, "] def\n";
915 # Font recoding routine
916 # newname fontname --
917 print "/nasmenc {\n";
918 print " findfont dup length dict begin\n";
919 print " { 1 index /FID ne {def}{pop pop} ifelse } forall\n";
920 print " /Encoding NASMEncoding def\n";
921 print " currentdict\n";
923 print " definefont pop\n";
926 # Emit fontset definitions
927 foreach $fset ( @AllFonts ) {
931 foreach $font ( @{$fset->{fonts}} ) {
932 $allfonts{$font->[1]->{name}}++;
934 foreach $font ( keys(%allfonts) ) {
935 print '/',$font,'-NASM /',$font," nasmenc\n";
937 foreach $font ( @{$fset->{fonts}} ) {
938 print '/', $fset->{name}, $i, ' ',
939 '/', $font->[1]->{name}, '-NASM findfont ',
940 $font->[0], " scalefont def\n";
941 push(@zfonts, $fset->{name}.$i);
944 print '/', $fset->{name}, ' [', join(' ',@zfonts), "] def\n";
947 # Emit the result as PostScript. This is *NOT* correct code yet!
948 open(PSHEAD, "< head.ps");
949 while ( defined($line = <PSHEAD>) ) {
953 print "%%EndProlog\n";
955 # Generate a PostScript string
960 my ($l) = length($s);
961 for ( $i = 0 ; $i < $l ; $i++ ) {
962 $c = substr($s,$i,1);
963 if ( ord($c) < 32 || ord($c) > 126 ) {
964 $o .= sprintf("\\%03o", ord($c));
965 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
974 # Generate PDF bookmarks
975 print "%%BeginSetup\n";
976 foreach $b ( @bookmarks ) {
977 print '[/Title ', ps_string($b->[2]), "\n";
978 print '/Count ', $b->[1], ' ' if ( $b->[1] );
979 print '/Dest /',$b->[0]," /OUT pdfmark\n";
982 # Ask the PostScript interpreter for the proper size media
983 print "setpagesize\n";
984 print "%%EndSetup\n";
986 # Start a PostScript page
987 sub ps_start_page() {
989 print "%%Page: $ps_page $ps_page\n";
990 print "%%BeginPageSetup\n";
992 print "%%EndPageSetup\n";
993 print '/', $ps_page, " pa\n";
996 # End a PostScript page
1000 print "($ps_page)", (($ps_page & 1) ? 'pageodd' : 'pageeven'), "\n";
1002 print "restore showpage\n";
1007 # Title page and inner cover
1009 $title = $metadata{'title'};
1010 $title =~ s/ \- / $emdash /;
1011 $pstitle = ps_string($title);
1013 # FIX THIS: This shouldn't be hard-coded like this
1015 lmarg pageheight 2 mul 3 div moveto
1017 /title linkdest ${pstitle} show
1018 lmarg pageheight 2 mul 3 div 10 sub moveto
1019 0 setlinecap 3 setlinewidth
1020 pagewidth lmarg sub rmarg sub 0 rlineto stroke
1024 /Courier-Bold findfont sz scalefont setfont
1027 [(-~~..~:\#;L .-:\#;L,.- .~:\#:;.T -~~.~:;. .~:;. )
1028 ( E8+U *T +U\' *T\# .97 *L E8+\' *;T\' *;, )
1029 ( D97 \`*L .97 \'*L \"T;E+:, D9 *L *L )
1030 ( H7 I\# T7 I\# \"*:. H7 I\# I\# )
1031 ( U: :8 *\#+ , :8 T, 79 U: :8 :8 )
1032 (,\#B. .IE, \"T;E* .IE, J *+;\#:T*\" ,\#B. .IE, .IE,)] {
1033 currentpoint 3 -1 roll
1034 sz -0.10 mul 0 3 -1 roll ashow
1035 sz 0.72 mul sub moveto
1040 pagewidth 2 div 143 sub
1041 pageheight 2 div 33 add
1048 foreach $line ( @pslines ) {
1049 my $linfo = $line->[0];
1051 if ( $$linfo[4] != $curpage ) {
1052 ps_end_page($curpage > 2);
1054 $curpage = $$linfo[4];
1059 foreach my $c ( @{$line->[1]} ) {
1060 if ( $$c[0] >= 0 ) {
1061 if ( $curfont != $$c[0] ) {
1062 print ($curfont = $$c[0]);
1064 print ps_string($$c[1]);
1065 } elsif ( $$c[0] == -1 ) {
1066 print '{el}'; # End link
1067 } elsif ( $$c[0] == -2 ) {
1068 print '{/',$$c[1],' xl}'; # xref link
1069 } elsif ( $$c[0] == -3 ) {
1070 print '{',ps_string($$c[1]),'wl}'; # web link
1071 } elsif ( $$c[0] == -4 ) {
1072 # Index anchor -- ignore
1073 } elsif ( $$c[0] == -5 ) {
1074 print '{/',$$c[1],' xa}'; #xref anchor
1075 } elsif ( $$c[0] == -6 ) {
1076 print ']['; # Start a new array
1078 } elsif ( $$c[0] == -7 ) {
1079 print '{/',$$c[1],' pl}'; # page link
1081 die "Unknown annotation";
1085 if ( defined($$linfo[2]) ) {
1086 foreach my $x ( @{$$linfo[2]} ) {
1087 if ( $$x[0] == $AuxStr ) {
1088 print ps_string($$x[1]);
1089 } elsif ( $$x[0] == $AuxPage ) {
1090 print $ps_xref_page{$$x[1]},' ';
1091 } elsif ( $$x[0] == $AuxPageStr ) {
1092 print ps_string($ps_xref_page{$$x[1]});
1093 } elsif ( $$x[0] == $AuxXRef ) {
1094 print '/',ps_xref($$x[1]),' ';
1095 } elsif ( $$x[0] == $AuxNum ) {
1098 die "Unknown auxilliary data type";
1102 print ($psconf{pageheight}-$psconf{topmarg}-$$linfo[5]);
1103 print ' ', $$linfo[6] if ( defined($$linfo[6]) );
1104 print ' ', $$linfo[0].$$linfo[1], "\n";