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 => 75, # 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 => 24, # 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 'a5' => [421, 595], # ISO half paper size
44 'b5' => [501, 709], # ISO small paper size
45 'a4' => [595, 842], # ISO standard paper size
46 'letter' => [612, 792], # US common paper size
47 'pa4' => [595, 792], # Compromise ("portable a4")
48 'b4' => [709,1002], # ISO intermediate paper size
49 'legal' => [612,1008], # US intermediate paper size
50 'a3' => [842,1190], # ISO double paper size
51 '11x17' => [792,1224], # US double paper size
55 # Parse the command line
58 while ( $arg = shift(@ARGV) ) {
59 if ( $arg =~ /^\-(|no\-)(.*)$/ ) {
61 $true = ($1 eq '') ? 1 : 0;
62 if ( $true && defined($papersizes{$parm}) ) {
63 $psconf{pagewidth} = $papersizes{$parm}->[0];
64 $psconf{pageheight} = $papersizes{$parm}->[1];
65 } elsif ( defined($psbool{$parm}) ) {
66 $psbool{$parm} = $true;
67 } elsif ( $true && defined($psconf{$parm}) ) {
68 $psconf{$parm} = shift(@ARGV);
70 die "$0: Unknown option: $arg\n";
78 # Document formatting parameters
80 $paraskip = 6; # Space between paragraphs
81 $chapstart = 30; # Space before a chapter heading
82 $chapskip = 24; # Space after a chapter heading
83 $tocskip = 6; # Space between TOC entries
85 # Configure post-paragraph skips for each kind of paragraph
86 %skiparray = ('chap' => $chapskip, 'appn' => $chapstart,
87 'head' => $paraskip, 'subh' => $paraskip,
88 'norm' => $paraskip, 'bull' => $paraskip,
89 'code' => $paraskip, 'toc0' => $tocskip,
90 'toc1' => $tocskip, 'toc2' => $tocskip);
93 # Custom encoding vector. This is basically the same as
94 # ISOLatin1Encoding (a level 2 feature, so we dont want to use it),
95 # but with a few extra characters thrown in. It is basically a
96 # modified Windows 1252 codepage, minus, for now, the euro sign (\200
97 # is reserved for euro.)
102 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
103 'ampersand', 'quoteright', 'parenleft',
104 'parenright', 'asterisk', 'plus', 'comma', 'minus',
105 'period', 'slash', 'zero', 'one', 'two', 'three',
106 'four', 'five', 'six', 'seven', 'eight', 'nine',
107 'colon', 'semicolon', 'less', 'equal', 'greater',
108 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
109 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
110 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
111 'bracketleft', 'backslash', 'bracketright',
112 'asciicircum', 'underscore', 'quoteleft', 'a', 'b',
113 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
114 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
115 'w', 'x', 'y', 'z', 'braceleft', 'bar',
116 'braceright', 'asciitilde', undef,
117 undef, 'macron', 'quotesinglbase', 'florin',
118 'quotedblbase', 'ellipsis', 'dagger', 'dbldagger',
119 'circumflex', 'perthousand', 'Scaron', 'guilsinglleft',
120 'OE', 'hungarumlaut', 'Zcaron', 'caron',
121 'ogonek', 'grave', 'quotesingle', 'quotedblleft',
122 'quotedblright', 'bullet', 'endash', 'emdash',
123 'tilde', 'trademark', 'scaron', 'guilsignlright',
124 'oe', 'ring', 'zcaron', 'Ydieresis',
125 'space', 'exclamdown', 'cent', 'sterling',
126 'currency', 'yen', 'brokenbar', 'section',
127 'dieresis', 'copyright', 'ordfeminine',
128 'guillemotleft', 'logicalnot', 'hyphen',
129 'registered', 'macron', 'degree', 'plusminus',
130 'twosuperior', 'threesuperior', 'acute', 'mu',
131 'paragraph', 'periodcentered', 'cedilla',
132 'onesuperior', 'ordmasculine', 'guillemotright',
133 'onequarter', 'onehalf', 'threequarters',
134 'questiondown', 'Agrave', 'Aacute', 'Acircumflex',
135 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla',
136 'Egrave', 'Eacute', 'Ecircumflex', 'Edieresis',
137 'Igrave', 'Iacute', 'Icircumflex', 'Idieresis',
138 'Eth', 'Ntilde', 'Ograve', 'Oacute', 'Ocircumflex',
139 'Otilde', 'Odieresis', 'multiply', 'Oslash',
140 'Ugrave', 'Uacute', 'Ucircumflex', 'Udieresis',
141 'Yacute', 'Thorn', 'germandbls', 'agrave', 'aacute',
142 'acircumflex', 'atilde', 'adieresis', 'aring', 'ae',
143 'ccedilla', 'egrave', 'eacute', 'ecircumflex',
144 'edieresis', 'igrave', 'iacute', 'icircumflex',
145 'idieresis', 'eth', 'ntilde', 'ograve', 'oacute',
146 'ocircumflex', 'otilde', 'odieresis', 'divide',
147 'oslash', 'ugrave', 'uacute', 'ucircumflex',
148 'udieresis', 'yacute', 'thorn', 'ydieresis'
157 # First, format the stuff coming from the front end into
158 # a cleaner representation
160 if ( defined($input) ) {
161 sysopen(PARAS, $input, O_RDONLY) or
162 die "$0: cannot open $input: $!\n";
164 open(PARAS, "<&STDIN") or die "$0: $!\n";
166 while ( defined($line = <PARAS>) ) {
170 if ( $line =~ /^meta :(.*)$/ ) {
172 $metadata{$metakey} = $data;
173 } elsif ( $line =~ /^indx :(.*)$/ ) {
175 push(@ixentries, $ixentry);
176 $ixterms{$ixentry} = [split(/\037/, $data)];
177 # Look for commas. This is easier done on the string
178 # representation, so do it now.
179 if ( $data =~ /^(.*)\,\037sp\037/ ) {
181 $ixprefix =~ s/\037n $//; # Discard possible font change at end
182 $ixhasprefix{$ixentry} = $ixprefix;
183 if ( !$ixprefixes{$ixprefix} ) {
184 $ixcommafirst{$ixentry}++;
186 $ixprefixes{$ixprefix}++;
188 # A complete term can also be used as a prefix
189 $ixprefixes{$data}++;
192 push(@ptypes, $line);
193 push(@paras, [split(/\037/, $data)]);
199 # Convert an integer to a chosen base
205 my($z) = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
206 return '0' if ($i == 0);
207 if ( $i < 0 ) { $n = '-'; $i = -$i; }
209 $s = substr($z,$i%$b,1) . $s;
216 # Convert a string to a rendering array
223 $s =~ s/ \- / $endash /g; # Replace " - " with en dash
225 while ( $s =~ /^(\s+|\S+)(.*)$/ ) {
234 # Take a crossreference name and generate the PostScript name for it.
236 # This hack produces a somewhat smaller PDF...
241 # my $q = $ps_xref_list{$s};
242 # return $q if ( defined($ps_xref_list{$s}) );
243 # $q = 'X'.int2base($ps_xref_next++, 52);
244 # $ps_xref_list{$s} = $q;
248 # Somewhat bigger PDF, but one which obeys # URLs
254 # Flow lines according to a particular font set and width
256 # A "font set" is represented as an array containing
257 # arrays of pairs: [<size>, <metricref>]
259 # Each line is represented as:
260 # [ [type,first|last,aux,fontset,page,ypos,optional col],
261 # [rendering array] ]
263 # A space character may be "squeezed" by up to this much
264 # (as a fraction of the normal width of a space.)
266 $ps_space_squeeze = 0.00; # Min space width 100%
267 sub ps_flow_lines($$$@) {
268 my($wid, $fontset, $type, @data) = @_;
269 my($fonts) = $$fontset{fonts};
271 my($w) = 0; # Width of current line
272 my($sw) = 0; # Width of current line due to spaces
273 my(@l) = (); # Current line
274 my(@ls) = (); # Accumulated output lines
275 my(@xd) = (); # Metadata that goes with subsequent text
276 my $hasmarker = 0; # Line has -6 marker
277 my $pastmarker = 0; # -6 marker found
279 # If there is a -6 marker anywhere in the paragraph,
280 # *each line* output needs to have a -6 marker
281 foreach $e ( @data ) {
282 $hasmarker = 1 if ( $$e[0] == -6 );
286 foreach $e ( @data ) {
288 # Type is metadata. Zero width.
289 if ( $$e[0] == -6 ) {
292 if ( $$e[0] == -1 || $$e[0] == -6 ) {
293 # -1 (end anchor) or -6 (marker) goes with the preceeding
294 # text, otherwise with the subsequent text
300 my $ew = ps_width($$e[1], $fontset->{fonts}->[$$e[0]][1],
302 ($fontset->{fonts}->[$$e[0]][0]/1000);
304 $sp =~ tr/[^ ]//d; # Delete nonspaces
305 my $esw = ps_width($sp, $fontset->{fonts}->[$$e[0]][1],
307 ($fontset->{fonts}->[$$e[0]][0]/1000);
309 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
311 # Search backwards for previous space chunk
312 my $lx = scalar(@l)-1;
315 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) {
317 $pastmarker = 0 if ( $l[$lx]->[0] == -6 );
321 if ( $l[$lx]->[1] eq ' ' ) {
323 @rm = splice(@l, $lx);
324 last; # Found place to break
331 # Now @l contains the stuff to remain on the old line
332 # If we broke the line inside a link, then split the link
335 foreach my $lc ( @l ) {
336 if ( $$lc[0] == -2 || $$lc[0] == -3 || $lc[0] == -7 ) {
338 } elsif ( $$lc[0] == -1 ) {
343 if ( defined($lkref) ) {
344 push(@l, [-1,undef]); # Terminate old reference
345 unshift(@rm, $lkref); # Duplicate reference on new line
350 unshift(@rm,[-6,undef]); # New line starts with marker
352 push(@l,[-6,undef]); # Old line ends with marker
356 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
360 # Compute the width of the remainder array
362 if ( $$le[0] >= 0 ) {
363 my $xew = ps_width($$le[1],
364 $fontset->{fonts}->[$$le[0]][1],
366 ($fontset->{fonts}->[$$le[0]][0]/1000);
368 $xsp =~ tr/[^ ]//d; # Delete nonspaces
369 my $xsw = ps_width($xsp,
370 $fontset->{fonts}->[$$le[0]][1],
372 ($fontset->{fonts}->[$$le[0]][0]/1000);
373 $w += $xew; $sw += $xsw;
377 push(@l, @xd); # Accumulated metadata
379 if ( $$e[1] ne '' ) {
381 $w += $ew; $sw += $esw;
387 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
390 # Mark the first line as first and the last line as last
392 $ls[0]->[0]->[1] |= 1; # First in para
393 $ls[-1]->[0]->[1] |= 2; # Last in para
399 # Once we have broken things into lines, having multiple chunks
400 # with the same font index is no longer meaningful. Merge
401 # adjacent chunks to keep down the size of the whole file.
403 sub ps_merge_chunks(@) {
410 $eco = -1; # Index of the last entry in @co
412 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
413 $co[$eco]->[1] .= $$c[1];
415 push(@co, $c); $eco++;
423 # Convert paragraphs to rendering arrays. Each
424 # element in the array contains (font, string),
425 # where font can be one of:
429 # -4 index item anchor
431 # -6 left/right marker (used in the index)
432 # -7 page link (used in the index)
435 # 2 code (fixed spacing)
438 sub mkparaarray($@) {
439 my($ptype, @chunks) = @_;
445 if ( $ptype =~ /^code/ ) {
446 foreach $chunk ( @chunks ) {
447 push(@para, [2, $chunk]);
450 foreach $chunk ( @chunks ) {
451 my $type = substr($chunk,0,2);
452 my $text = substr($chunk,2);
454 if ( $type eq 'sp' ) {
455 push(@para, [$in_e?1:0, ' ']);
456 } elsif ( $type eq 'da' ) {
457 push(@para, [$in_e?1:0, $endash]);
458 } elsif ( $type eq 'n ' ) {
459 push(@para, [0, $text]);
461 } elsif ( $type =~ '^e' ) {
462 push(@para, [1, $text]);
463 $in_e = ($type eq 'es' || $type eq 'e ');
464 } elsif ( $type eq 'c ' ) {
465 push(@para, [2, $text]);
467 } elsif ( $type eq 'x ' ) {
468 push(@para, [-2, ps_xref($text)]);
469 } elsif ( $type eq 'xe' ) {
470 push(@para, [-1, undef]);
471 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
472 $text =~ /\<(.*)\>(.*)$/;
473 my $link = $1; $text = $2;
474 push(@para, [-3, $link]);
475 push(@para, [($type eq 'wc') ? 2:0, $text]);
476 push(@para, [-1, undef]);
478 } elsif ( $type eq 'i ' ) {
479 push(@para, [-4, $text]);
481 die "Unexpected paragraph chunk: $chunk";
488 $npara = scalar(@paras);
489 for ( $i = 0 ; $i < $npara ; $i++ ) {
490 $paras[$i] = [mkparaarray($ptypes[$i], @{$paras[$i]})];
494 # This converts a rendering array to a simple string
496 sub ps_arraytostr(@) {
500 $s .= $$c[1] if ( $$c[0] >= 0 );
506 # This generates a duplicate of a paragraph
521 # This generates a duplicate of a paragraph, stripping anchor
524 sub ps_dup_para_noanchor(@) {
531 push(@o, [@cc]) unless ( $cc[0] == -4 || $cc[0] == -5 );
537 # Scan for header paragraphs and fix up their contents;
538 # also generate table of contents and PDF bookmarks.
540 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
541 @tocptypes = ('chap');
542 @bookmarks = (['title', 0, 'Title'], ['contents', 0, 'Contents']);
544 for ( $i = 0 ; $i < $npara ; $i++ ) {
545 my $xtype = $ptypes[$i];
546 my $ptype = substr($xtype,0,4);
550 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
551 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
556 my $xref = ps_xref($sech);
557 my $chap = ($ptype eq 'chap')?'Chapter':'Appendix';
559 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
560 push(@bookmarks, $book);
561 $bookref{$secn} = $book;
563 push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
564 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
566 unshift(@{$paras[$i]},
567 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
568 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
569 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
574 my $xref = ps_xref($sech);
576 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
578 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
579 push(@bookmarks, $book);
580 $bookref{$secn} = $book;
581 $bookref{$pref}->[1]--; # Adjust count for parent node
583 push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
585 (($ptype eq 'subh') ? 'toc2':'toc1').' :'.$sech.':'.$secn);
587 unshift(@{$paras[$i]}, [-5, $xref]);
592 # Add TOC to beginning of paragraph list
594 unshift(@paras, @tocparas); undef @tocparas;
595 unshift(@ptypes, @tocptypes); undef @tocptypes;
598 # Add copyright notice to the beginning
601 [[0, $copyright], [0, ' '], [0,$metadata{'year'}],
602 [0, ' '], string2array($metadata{'author'})],
603 [string2array($metadata{'license'})]);
604 unshift(@ptypes, 'norm', 'norm');
606 $npara = scalar(@paras);
609 # No lines generated, yet.
614 # Line Auxilliary Information Types
616 $AuxStr = 1; # String
617 $AuxPage = 2; # Page number (from xref)
618 $AuxPageStr = 3; # Page number as a PostScript string
619 $AuxXRef = 4; # Cross reference as a name
620 $AuxNum = 5; # Number
623 # Break or convert paragraphs into lines, and push them
624 # onto the @pslines array.
626 sub ps_break_lines($$) {
627 my ($paras,$ptypes) = @_;
629 my $linewidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
630 my $bullwidth = $linewidth-$psconf{bulladj};
631 my $indxwidth = ($linewidth-$psconf{idxgutter})/$psconf{idxcolumns}
634 my $npara = scalar(@{$paras});
637 for ( $i = 0 ; $i < $npara ; $i++ ) {
638 my $xtype = $ptypes->[$i];
639 my $ptype = substr($xtype,0,4);
640 my @data = @{$paras->[$i]};
642 if ( $ptype eq 'code' ) {
644 # Code paragraph; each chunk is a line
645 foreach $p ( @data ) {
646 push(@ls, [[$ptype,0,undef,\%BodyFont,0,0],[$p]]);
648 $ls[0]->[0]->[1] |= 1; # First in para
649 $ls[-1]->[0]->[1] |= 2; # Last in para
650 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
651 # Chapters are flowed normally, but in an unusual font
652 @ls = ps_flow_lines($linewidth, \%ChapFont, $ptype, @data);
653 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
654 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
659 my $font = ($ptype eq 'head') ? \%HeadFont : \%SubhFont;
660 @ls = ps_flow_lines($linewidth, $font, $ptype, @data);
661 # We need the heading number as auxillary data
662 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
663 } elsif ( $ptype eq 'norm' ) {
664 @ls = ps_flow_lines($linewidth, \%BodyFont, $ptype, @data);
665 } elsif ( $ptype eq 'bull' ) {
666 @ls = ps_flow_lines($bullwidth, \%BodyFont, $ptype, @data);
667 } elsif ( $ptype =~ /^toc/ ) {
668 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
672 my $refname = $2.' ';
673 my $ntoc = substr($ptype,3,1)+0;
674 my $refwidth = ps_width($refname, $BodyFont{fonts}->[0][1],
676 ($BodyFont{fonts}->[0][0]/1000);
678 @ls = ps_flow_lines($linewidth-$ntoc*$psconf{tocind}-
679 $psconf{tocpnz}-$refwidth,
680 \%BodyFont, $ptype, @data);
682 # Auxilliary data: for the first line, the cross reference symbol
683 # and the reference name; for all lines but the first, the
684 # reference width; and for the last line, the page number
686 my $nl = scalar(@ls);
687 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
688 for ( $j = 1 ; $j < $nl ; $j++ ) {
689 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
691 push(@{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
692 } elsif ( $ptype =~ /^idx/ ) {
693 my $lvl = substr($ptype,3,1)+0;
695 @ls = ps_flow_lines($indxwidth-$lvl*$psconf{idxindent},
696 \%BodyFont, $ptype, @data);
698 die "Unknown para type: $ptype";
700 # Merge adjacent identical chunks
702 @{$$l[1]} = ps_merge_chunks(@{$$l[1]});
708 # Break the main body text into lines.
709 ps_break_lines(\@paras, \@ptypes);
712 # Break lines in to pages
715 # Where to start on page 2, the copyright page
716 $curpage = 2; # Start on page 2
717 $curypos = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg}-
718 $psconf{startcopyright};
719 undef $columnstart; # Not outputting columnar text
720 undef $curcolumn; # Current column
721 $nlines = scalar(@pslines);
724 # This formats lines inside the global @pslines array into pages,
725 # updating the page and y-coordinate entries. Start at the
726 # $startline position in @pslines and go to but not including
727 # $endline. The global variables $curpage, $curypos, $columnstart
728 # and $curcolumn are updated appropriately.
730 sub ps_break_pages($$) {
731 my($startline, $endline) = @_;
733 # Paragraph types which should never be broken
734 my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
735 # Paragraph types which are heading (meaning they should not be broken
737 my $nobreakafter = "^(chap|appn|head|subh)\$";
738 # Paragraph types which should never be broken *before*
739 my $nobreakbefore = "^idx[1-9]\$";
740 # Paragraph types which are set in columnar format
741 my $columnregexp = "^idx.\$";
743 my $upageheight = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg};
747 for ( $i = $startline ; $i < $endline ; $i++ ) {
748 my $linfo = $pslines[$i]->[0];
749 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
750 && ($$linfo[1] & 1) ) {
751 # First line of a new chapter heading. Start a new page.
753 $curpage++ if ( $curypos > 0 || defined($columnstart) );
754 $curypos = $chapstart;
755 } elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
761 if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
762 $columnstart = $curypos;
766 # Adjust position by the appropriate leading
767 $curypos += $$linfo[3]->{leading};
769 # Record the page and y-position
770 $$linfo[4] = $curpage;
771 $$linfo[5] = $curypos;
772 $$linfo[6] = $curcolumn if ( defined($columnstart) );
774 if ( $curypos > $upageheight ) {
775 # We need to break the page before this line.
776 my $broken = 0; # No place found yet
777 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
778 my $linfo = $pslines[$i]->[0];
779 my $pinfo = $pslines[$i-1]->[0];
781 if ( $$linfo[1] == 2 ) {
782 # This would be an orphan, don't break.
783 } elsif ( $$linfo[1] & 1 ) {
784 # Sole line or start of paragraph. Break unless
785 # the previous line was part of a heading.
786 $broken = 1 if ( $$pinfo[0] !~ /$nobreakafter/o &&
787 $$linfo[0] !~ /$nobreakbefore/o );
789 # Middle of paragraph. Break unless we're in a
790 # no-break paragraph, or the previous line would
791 # end up being a widow.
792 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
797 die "Nowhere to break page $curpage\n" if ( !$broken );
798 # Now $i should point to line immediately before the break, i.e.
799 # the next paragraph should be the first on the new page
800 if ( defined($columnstart) &&
801 ++$curcolumn < $psconf{idxcolumns} ) {
802 # We're actually breaking text into columns, not pages
803 $curypos = $columnstart;
812 # Add end of paragraph skip
813 if ( $$linfo[1] & 2 ) {
814 $curypos += $skiparray{$$linfo[0]};
819 ps_break_pages(0,$nlines); # Break the main text body into pages
822 # Find the page number of all the indices
824 %ps_xref_page = (); # Crossref anchor pages
825 %ps_index_pages = (); # Index item pages
826 $nlines = scalar(@pslines);
827 for ( $i = 0 ; $i < $nlines ; $i++ ) {
828 my $linfo = $pslines[$i]->[0];
829 foreach my $c ( @{$pslines[$i]->[1]} ) {
830 if ( $$c[0] == -4 ) {
831 if ( !defined($ps_index_pages{$$c[1]}) ) {
832 $ps_index_pages{$$c[1]} = [];
833 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
834 # Pages are emitted in order; if this is a duplicated
835 # entry it will be the last one
838 push(@{$ps_index_pages{$$c[1]}}, $$linfo[4]);
839 } elsif ( $$c[0] == -5 ) {
840 $ps_xref_page{$$c[1]} = $$linfo[4];
846 # Emit index paragraphs
848 $startofindex = scalar(@pslines);
849 @ixparas = ([[-5,'index'],[0,'Index']]);
850 @ixptypes = ('chap');
852 foreach $k ( @ixentries ) {
854 my $ixptype = 'idx0';
855 my $prefix = $ixhasprefix{$k};
856 my @ixpara = mkparaarray($ixptype,@{$ixterms{$k}});
857 my $commapos = undef;
859 if ( defined($prefix) && $ixprefixes{$prefix} > 1 ) {
860 # This entry has a "hanging comma"
861 for ( $i = 0 ; $i < scalar(@ixpara)-1 ; $i++ ) {
862 if ( substr($ixpara[$i]->[1],-1,1) eq ',' &&
863 $ixpara[$i+1]->[1] eq ' ' ) {
869 if ( defined($commapos) ) {
870 if ( $ixcommafirst{$k} ) {
871 # This is the first entry; generate the
872 # "hanging comma" entry
873 my @precomma = splice(@ixpara,0,$commapos);
874 if ( $ixpara[0]->[1] eq ',' ) {
875 shift(@ixpara); # Discard lone comma
877 # Discard attached comma
878 $ixpara[0]->[1] =~ s/\,$//;
879 push(@precomma,shift(@ixpara));
881 push(@precomma, [-6,undef]);
882 push(@ixparas, [@precomma]);
883 push(@ixptypes, $ixptype);
884 shift(@ixpara); # Remove space
886 splice(@ixpara,0,$commapos+2);
891 push(@ixpara, [-6,undef]); # Left/right marker
892 $i = 1; $n = scalar(@{$ps_index_pages{$k}});
893 foreach $p ( @{$ps_index_pages{$k}} ) {
895 push(@ixpara,[-7,$p],[0,"$p"],[-1,undef]);
897 push(@ixpara,[-7,$p],[0,"$p,"],[-1,undef],[0,' ']);
901 push(@ixparas, [@ixpara]);
902 push(@ixptypes, $ixptype);
906 # Flow index paragraphs into lines
908 ps_break_lines(\@ixparas, \@ixptypes);
911 # Format index into pages
913 $nlines = scalar(@pslines);
914 ps_break_pages($startofindex, $nlines);
917 # Push index onto bookmark list
919 push(@bookmarks, ['index', 0, 'Index']);
921 # Get the list of fonts used
923 foreach $fset ( @AllFonts ) {
924 foreach $font ( @{$fset->{fonts}} ) {
925 $ps_all_fonts{$font->[1]->{name}}++;
929 # Emit the PostScript DSC header
930 print "%!PS-Adobe-3.0\n";
931 print "%%Pages: $curpage\n";
932 print "%%BoundingBox: 0 0 ", $psconf{pagewidth}, ' ', $psconf{pageheight}, "\n";
933 print "%%Creator: (NASM psflow.pl)\n";
934 print "%%DocumentData: Clean7Bit\n";
935 print "%%DocumentFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
936 print "%%DocumentNeededFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
937 print "%%Orientation: Portrait\n";
938 print "%%PageOrder: Ascend\n";
939 print "%%EndComments\n";
940 print "%%BeginProlog\n";
942 # Emit the configurables as PostScript tokens
943 foreach $c ( keys(%psconf) ) {
944 print "/$c ", $psconf{$c}, " def\n";
946 foreach $c ( keys(%psbool) ) {
947 print "/$c ", ($psbool{$c}?'true':'false'), " def\n";
950 # Emit custom encoding vector
951 $zstr = '/NASMEncoding [ ';
952 foreach $c ( @NASMEncoding ) {
953 my $z = '/'.(defined($c)?$c:'.notdef ').' ';
954 if ( length($zstr)+length($z) > 72 ) {
960 print $zstr, "] def\n";
962 # Font recoding routine
963 # newname fontname --
964 print "/nasmenc {\n";
965 print " findfont dup length dict begin\n";
966 print " { 1 index /FID ne {def}{pop pop} ifelse } forall\n";
967 print " /Encoding NASMEncoding def\n";
968 print " currentdict\n";
970 print " definefont pop\n";
973 # Emit fontset definitions
974 foreach $fset ( @AllFonts ) {
978 foreach $font ( @{$fset->{fonts}} ) {
979 $allfonts{$font->[1]->{name}}++;
981 foreach $font ( keys(%allfonts) ) {
982 print '/',$font,'-NASM /',$font," nasmenc\n";
984 foreach $font ( @{$fset->{fonts}} ) {
985 print '/', $fset->{name}, $i, ' ',
986 '/', $font->[1]->{name}, '-NASM findfont ',
987 $font->[0], " scalefont def\n";
988 push(@zfonts, $fset->{name}.$i);
991 print '/', $fset->{name}, ' [', join(' ',@zfonts), "] def\n";
994 # Emit the canned PostScript prologue
995 open(PSHEAD, "< head.ps");
996 while ( defined($line = <PSHEAD>) ) {
1000 print "%%EndProlog\n";
1002 # Generate a PostScript string
1007 my ($l) = length($s);
1008 for ( $i = 0 ; $i < $l ; $i++ ) {
1009 $c = substr($s,$i,1);
1010 if ( ord($c) < 32 || ord($c) > 126 ) {
1011 $o .= sprintf("\\%03o", ord($c));
1012 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
1021 # Generate PDF bookmarks
1022 print "%%BeginSetup\n";
1023 foreach $b ( @bookmarks ) {
1024 print '[/Title ', ps_string($b->[2]), "\n";
1025 print '/Count ', $b->[1], ' ' if ( $b->[1] );
1026 print '/Dest /',$b->[0]," /OUT pdfmark\n";
1029 # Ask the PostScript interpreter for the proper size media
1030 print "setpagesize\n";
1031 print "%%EndSetup\n";
1033 # Start a PostScript page
1034 sub ps_start_page() {
1036 print "%%Page: $ps_page $ps_page\n";
1037 print "%%BeginPageSetup\n";
1039 print "%%EndPageSetup\n";
1040 print '/', $ps_page, " pa\n";
1043 # End a PostScript page
1044 sub ps_end_page($) {
1047 print "($ps_page)", (($ps_page & 1) ? 'pageodd' : 'pageeven'), "\n";
1049 print "restore showpage\n";
1056 $title = $metadata{'title'};
1057 $title =~ s/ \- / $emdash /;
1058 $pstitle = ps_string($title);
1061 print "lmarg pageheight 2 mul 3 div moveto\n";
1062 print "tfont0 setfont\n";
1063 print "/title linkdest ${pstitle} show\n";
1064 print "lmarg pageheight 2 mul 3 div 10 sub moveto\n";
1065 print "0 setlinecap 3 setlinewidth\n";
1066 print "pagewidth lmarg sub rmarg sub 0 rlineto stroke\n";
1068 # Print logo, if there is one
1069 # FIX: To be 100% correct, this should look for DocumentNeeded*
1070 # and DocumentFonts in the header of the EPSF and add those to the
1072 if ( defined($metadata{epslogo}) &&
1073 sysopen(EPS, $metadata{epslogo}, O_RDONLY) ) {
1075 my ($bbllx,$bblly,$bburx,$bbury) = (undef,undef,undef,undef);
1078 my $maxwidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
1079 my $maxheight = $psconf{pageheight}/3-40;
1083 while ( defined($line = <EPS>) ) {
1084 last if ( $line =~ /^%%EOF/ );
1085 if ( !defined($bbllx) &&
1086 $line =~ /^\%\%BoundingBox\:\s*([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/i ) {
1087 $bbllx = $1+0; $bblly = $2+0;
1088 $bburx = $3+0; $bbury = $4+0;
1094 if ( defined($bbllx) ) {
1095 $width = $bburx-$bbllx;
1096 $height = $bbury-$bblly;
1098 if ( $width > $maxwidth ) {
1099 $scale = $maxwidth/$width;
1101 if ( $height*$scale > $maxheight ) {
1102 $scale = $maxheight/$height;
1105 $x = ($psconf{pagewidth}-$width*$scale)/2;
1106 $y = ($psconf{pageheight}-$height*$scale)/2;
1108 print "BeginEPSF\n";
1109 print $x, ' ', $y, " translate\n";
1110 print $scale, " dup scale\n" unless ( $scale == 1 );
1111 print -$bbllx, ' ', -$bblly, " translate\n";
1112 print "$bbllx $bblly moveto\n";
1113 print "$bburx $bblly lineto\n";
1114 print "$bburx $bbury lineto\n";
1115 print "$bbllx $bbury lineto\n";
1116 print "$bbllx $bblly lineto clip newpath\n";
1117 print "%%BeginDocument: ",ps_string($metadata{epslogo}),"\n";
1119 print "%%EndDocument\n";
1125 # Emit the rest of the document (page 2 and on)
1128 foreach $line ( @pslines ) {
1129 my $linfo = $line->[0];
1131 if ( $$linfo[4] != $curpage ) {
1132 ps_end_page($curpage > 2);
1134 $curpage = $$linfo[4];
1139 foreach my $c ( @{$line->[1]} ) {
1140 if ( $$c[0] >= 0 ) {
1141 if ( $curfont != $$c[0] ) {
1142 print ($curfont = $$c[0]);
1144 print ps_string($$c[1]);
1145 } elsif ( $$c[0] == -1 ) {
1146 print '{el}'; # End link
1147 } elsif ( $$c[0] == -2 ) {
1148 print '{/',$$c[1],' xl}'; # xref link
1149 } elsif ( $$c[0] == -3 ) {
1150 print '{',ps_string($$c[1]),'wl}'; # web link
1151 } elsif ( $$c[0] == -4 ) {
1152 # Index anchor -- ignore
1153 } elsif ( $$c[0] == -5 ) {
1154 print '{/',$$c[1],' xa}'; #xref anchor
1155 } elsif ( $$c[0] == -6 ) {
1156 print ']['; # Start a new array
1158 } elsif ( $$c[0] == -7 ) {
1159 print '{/',$$c[1],' pl}'; # page link
1161 die "Unknown annotation";
1165 if ( defined($$linfo[2]) ) {
1166 foreach my $x ( @{$$linfo[2]} ) {
1167 if ( $$x[0] == $AuxStr ) {
1168 print ps_string($$x[1]);
1169 } elsif ( $$x[0] == $AuxPage ) {
1170 print $ps_xref_page{$$x[1]},' ';
1171 } elsif ( $$x[0] == $AuxPageStr ) {
1172 print ps_string($ps_xref_page{$$x[1]});
1173 } elsif ( $$x[0] == $AuxXRef ) {
1174 print '/',ps_xref($$x[1]),' ';
1175 } elsif ( $$x[0] == $AuxNum ) {
1178 die "Unknown auxilliary data type";
1182 print ($psconf{pageheight}-$psconf{topmarg}-$$linfo[5]);
1183 print ' ', $$linfo[6] if ( defined($$linfo[6]) );
1184 print ' ', $$linfo[0].$$linfo[1], "\n";