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 => 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 '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 ( $data =~ /^(.*)\,\037sp\037/ ) {
178 $ixprefix =~ s/\037n $//; # Discard possible font change at end
179 $ixhasprefix{$ixentry} = $ixprefix;
180 if ( !$ixprefixes{$ixprefix} ) {
181 $ixcommafirst{$ixentry}++;
183 $ixprefixes{$ixprefix}++;
185 # A complete term can also be used as a prefix
186 $ixprefixes{$data}++;
189 push(@ptypes, $line);
190 push(@paras, [split(/\037/, $data)]);
196 # Convert an integer to a chosen base
202 my($z) = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
203 return '0' if ($i == 0);
204 if ( $i < 0 ) { $n = '-'; $i = -$i; }
206 $s = substr($z,$i%$b,1) . $s;
213 # Convert a string to a rendering array
220 while ( $s =~ /^(\s+|\S+)(.*)$/ ) {
229 # Take a crossreference name and generate the PostScript name for it.
231 # This hack produces a somewhat smaller PDF...
236 # my $q = $ps_xref_list{$s};
237 # return $q if ( defined($ps_xref_list{$s}) );
238 # $q = 'X'.int2base($ps_xref_next++, 52);
239 # $ps_xref_list{$s} = $q;
243 # Somewhat bigger PDF, but one which obeys # URLs
249 # Flow lines according to a particular font set and width
251 # A "font set" is represented as an array containing
252 # arrays of pairs: [<size>, <metricref>]
254 # Each line is represented as:
255 # [ [type,first|last,aux,fontset,page,ypos,optional col],
256 # [rendering array] ]
258 # A space character may be "squeezed" by up to this much
259 # (as a fraction of the normal width of a space.)
261 $ps_space_squeeze = 0.00; # Min space width 100%
262 sub ps_flow_lines($$$@) {
263 my($wid, $fontset, $type, @data) = @_;
264 my($fonts) = $$fontset{fonts};
266 my($w) = 0; # Width of current line
267 my($sw) = 0; # Width of current line due to spaces
268 my(@l) = (); # Current line
269 my(@ls) = (); # Accumulated output lines
270 my(@xd) = (); # Metadata that goes with subsequent text
271 my $hasmarker = 0; # Line has -6 marker
272 my $pastmarker = 0; # -6 marker found
274 # If there is a -6 marker anywhere in the paragraph,
275 # *each line* output needs to have a -6 marker
276 foreach $e ( @data ) {
277 $hasmarker = 1 if ( $$e[0] == -6 );
281 foreach $e ( @data ) {
283 # Type is metadata. Zero width.
284 if ( $$e[0] == -6 ) {
287 if ( $$e[0] == -1 || $$e[0] == -6 ) {
288 # -1 (end anchor) or -6 (marker) goes with the preceeding
289 # text, otherwise with the subsequent text
295 my $ew = ps_width($$e[1], $fontset->{fonts}->[$$e[0]][1],
297 ($fontset->{fonts}->[$$e[0]][0]/1000);
299 $sp =~ tr/[^ ]//d; # Delete nonspaces
300 my $esw = ps_width($sp, $fontset->{fonts}->[$$e[0]][1],
302 ($fontset->{fonts}->[$$e[0]][0]/1000);
304 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
306 # Search backwards for previous space chunk
307 my $lx = scalar(@l)-1;
310 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) {
312 $pastmarker = 0 if ( $l[$lx]->[0] == -6 );
316 if ( $l[$lx]->[1] eq ' ' ) {
318 @rm = splice(@l, $lx);
319 last; # Found place to break
326 # Now @l contains the stuff to remain on the old line
327 # If we broke the line inside a link, then split the link
330 foreach my $lc ( @l ) {
331 if ( $$lc[0] == -2 || $$lc[0] == -3 || $lc[0] == -7 ) {
333 } elsif ( $$lc[0] == -1 ) {
338 if ( defined($lkref) ) {
339 push(@l, [-1,undef]); # Terminate old reference
340 unshift(@rm, $lkref); # Duplicate reference on new line
345 unshift(@rm,[-6,undef]); # New line starts with marker
347 push(@l,[-6,undef]); # Old line ends with marker
351 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
355 # Compute the width of the remainder array
357 if ( $$le[0] >= 0 ) {
358 my $xew = ps_width($$le[1],
359 $fontset->{fonts}->[$$le[0]][1],
361 ($fontset->{fonts}->[$$le[0]][0]/1000);
363 $xsp =~ tr/[^ ]//d; # Delete nonspaces
364 my $xsw = ps_width($xsp,
365 $fontset->{fonts}->[$$le[0]][1],
367 ($fontset->{fonts}->[$$le[0]][0]/1000);
368 $w += $xew; $sw += $xsw;
372 push(@l, @xd); # Accumulated metadata
374 if ( $$e[1] ne '' ) {
376 $w += $ew; $sw += $esw;
382 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
385 # Mark the first line as first and the last line as last
387 $ls[0]->[0]->[1] |= 1; # First in para
388 $ls[-1]->[0]->[1] |= 2; # Last in para
394 # Once we have broken things into lines, having multiple chunks
395 # with the same font index is no longer meaningful. Merge
396 # adjacent chunks to keep down the size of the whole file.
398 sub ps_merge_chunks(@) {
405 $eco = -1; # Index of the last entry in @co
407 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
408 $co[$eco]->[1] .= $$c[1];
410 push(@co, $c); $eco++;
418 # Convert paragraphs to rendering arrays. Each
419 # element in the array contains (font, string),
420 # where font can be one of:
424 # -4 index item anchor
426 # -6 left/right marker (used in the index)
427 # -7 page link (used in the index)
430 # 2 code (fixed spacing)
433 sub mkparaarray($@) {
434 my($ptype, @chunks) = @_;
440 if ( $ptype =~ /^code/ ) {
441 foreach $chunk ( @chunks ) {
442 push(@para, [2, $chunk]);
445 foreach $chunk ( @chunks ) {
446 my $type = substr($chunk,0,2);
447 my $text = substr($chunk,2);
449 if ( $type eq 'sp' ) {
450 push(@para, [$in_e?1:0, ' ']);
451 } elsif ( $type eq 'da' ) {
452 push(@para, [$in_e?1:0, $endash]);
453 } elsif ( $type eq 'n ' ) {
454 push(@para, [0, $text]);
456 } elsif ( $type =~ '^e' ) {
457 push(@para, [1, $text]);
458 $in_e = ($type eq 'es' || $type eq 'e ');
459 } elsif ( $type eq 'c ' ) {
460 push(@para, [2, $text]);
462 } elsif ( $type eq 'x ' ) {
463 push(@para, [-2, ps_xref($text)]);
464 } elsif ( $type eq 'xe' ) {
465 push(@para, [-1, undef]);
466 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
467 $text =~ /\<(.*)\>(.*)$/;
468 my $link = $1; $text = $2;
469 push(@para, [-3, $link]);
470 push(@para, [($type eq 'wc') ? 2:0, $text]);
471 push(@para, [-1, undef]);
473 } elsif ( $type eq 'i ' ) {
474 push(@para, [-4, $text]);
476 die "Unexpected paragraph chunk: $chunk";
483 $npara = scalar(@paras);
484 for ( $i = 0 ; $i < $npara ; $i++ ) {
485 $paras[$i] = [mkparaarray($ptypes[$i], @{$paras[$i]})];
489 # This converts a rendering array to a simple string
491 sub ps_arraytostr(@) {
495 $s .= $$c[1] if ( $$c[0] >= 0 );
501 # This generates a duplicate of a paragraph
516 # This generates a duplicate of a paragraph, stripping anchor
519 sub ps_dup_para_noanchor(@) {
526 push(@o, [@cc]) unless ( $cc[0] == -4 || $cc[0] == -5 );
532 # Scan for header paragraphs and fix up their contents;
533 # also generate table of contents and PDF bookmarks.
535 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
536 @tocptypes = ('chap');
537 @bookmarks = (['title', 0, 'Title Page'], ['contents', 0, 'Contents']);
539 for ( $i = 0 ; $i < $npara ; $i++ ) {
540 my $xtype = $ptypes[$i];
541 my $ptype = substr($xtype,0,4);
545 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
546 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
551 my $xref = ps_xref($sech);
552 my $chap = ($ptype eq 'chap')?'Chapter':'Appendix';
554 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
555 push(@bookmarks, $book);
556 $bookref{$secn} = $book;
558 push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
559 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
561 unshift(@{$paras[$i]},
562 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
563 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
564 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
569 my $xref = ps_xref($sech);
571 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
573 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
574 push(@bookmarks, $book);
575 $bookref{$secn} = $book;
576 $bookref{$pref}->[1]--; # Adjust count for parent node
578 push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
580 (($ptype eq 'subh') ? 'toc2':'toc1').' :'.$sech.':'.$secn);
582 unshift(@{$paras[$i]}, [-5, $xref]);
587 # Add TOC to beginning of paragraph list
589 unshift(@paras, @tocparas); undef @tocparas;
590 unshift(@ptypes, @tocptypes); undef @tocptypes;
593 # Add copyright notice to the beginning
595 unshift(@paras, [[0, "\251"], [0, ' '], [0,$metadata{'year'}],
596 [0, ' '], string2array($metadata{'author'})],
597 [[0, ' ']], [string2array($metadata{'license'})]);
598 unshift(@ptypes, 'norm', 'norm', 'norm');
600 $npara = scalar(@paras);
603 # No lines generated, yet.
608 # Line Auxilliary Information Types
610 $AuxStr = 1; # String
611 $AuxPage = 2; # Page number (from xref)
612 $AuxPageStr = 3; # Page number as a PostScript string
613 $AuxXRef = 4; # Cross reference as a name
614 $AuxNum = 5; # Number
617 # Break or convert paragraphs into lines, and push them
618 # onto the @pslines array.
620 sub ps_break_lines($$) {
621 my ($paras,$ptypes) = @_;
623 my $linewidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
624 my $bullwidth = $linewidth-$psconf{bulladj};
625 my $indxwidth = ($linewidth-$psconf{idxgutter})/$psconf{idxcolumns}
628 my $npara = scalar(@{$paras});
631 for ( $i = 0 ; $i < $npara ; $i++ ) {
632 my $xtype = $ptypes->[$i];
633 my $ptype = substr($xtype,0,4);
634 my @data = @{$paras->[$i]};
636 if ( $ptype eq 'code' ) {
638 # Code paragraph; each chunk is a line
639 foreach $p ( @data ) {
640 push(@ls, [[$ptype,0,undef,\%BodyFont,0,0],[$p]]);
642 $ls[0]->[0]->[1] |= 1; # First in para
643 $ls[-1]->[0]->[1] |= 2; # Last in para
644 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
645 # Chapters are flowed normally, but in an unusual font
646 @ls = ps_flow_lines($linewidth, \%ChapFont, $ptype, @data);
647 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
648 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
653 my $font = ($ptype eq 'head') ? \%HeadFont : \%SubhFont;
654 @ls = ps_flow_lines($linewidth, $font, $ptype, @data);
655 # We need the heading number as auxillary data
656 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
657 } elsif ( $ptype eq 'norm' ) {
658 @ls = ps_flow_lines($linewidth, \%BodyFont, $ptype, @data);
659 } elsif ( $ptype eq 'bull' ) {
660 @ls = ps_flow_lines($bullwidth, \%BodyFont, $ptype, @data);
661 } elsif ( $ptype =~ /^toc/ ) {
662 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
666 my $refname = $2.' ';
667 my $ntoc = substr($ptype,3,1)+0;
668 my $refwidth = ps_width($refname, $BodyFont{fonts}->[0][1],
670 ($BodyFont{fonts}->[0][0]/1000);
672 @ls = ps_flow_lines($linewidth-$ntoc*$psconf{tocind}-
673 $psconf{tocpnz}-$refwidth,
674 \%BodyFont, $ptype, @data);
676 # Auxilliary data: for the first line, the cross reference symbol
677 # and the reference name; for all lines but the first, the
678 # reference width; and for the last line, the page number
680 my $nl = scalar(@ls);
681 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
682 for ( $j = 1 ; $j < $nl ; $j++ ) {
683 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
685 push(@{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
686 } elsif ( $ptype =~ /^idx/ ) {
687 my $lvl = substr($ptype,3,1)+0;
689 @ls = ps_flow_lines($indxwidth-$lvl*$psconf{idxindent},
690 \%BodyFont, $ptype, @data);
692 die "Unknown para type: $ptype";
694 # Merge adjacent identical chunks
696 @{$$l[1]} = ps_merge_chunks(@{$$l[1]});
702 # Break the main body text into lines.
703 ps_break_lines(\@paras, \@ptypes);
706 # Break lines in to pages
709 # Where to start on page 2, the copyright page
710 $curpage = 2; # Start on page 2
711 $curypos = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg}-
712 $psconf{startcopyright};
713 undef $columnstart; # Not outputting columnar text
714 undef $curcolumn; # Current column
715 $nlines = scalar(@pslines);
718 # This formats lines inside the global @pslines array into pages,
719 # updating the page and y-coordinate entries. Start at the
720 # $startline position in @pslines and go to but not including
721 # $endline. The global variables $curpage, $curypos, $columnstart
722 # and $curcolumn are updated appropriately.
724 sub ps_break_pages($$) {
725 my($startline, $endline) = @_;
727 # Paragraph types which should never be broken
728 my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
729 # Paragraph types which are heading (meaning they should not be broken
731 my $nobreakafter = "^(chap|appn|head|subh)\$";
732 # Paragraph types which should never be broken *before*
733 my $nobreakbefore = "^idx[1-9]\$";
734 # Paragraph types which are set in columnar format
735 my $columnregexp = "^idx.\$";
737 my $upageheight = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg};
741 for ( $i = $startline ; $i < $endline ; $i++ ) {
742 my $linfo = $pslines[$i]->[0];
743 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
744 && ($$linfo[1] & 1) ) {
745 # First line of a new chapter heading. Start a new page.
747 $curpage++ if ( $curypos > 0 || defined($columnstart) );
748 $curypos = $chapstart;
749 } elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
755 if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
756 $columnstart = $curypos;
760 # Adjust position by the appropriate leading
761 $curypos += $$linfo[3]->{leading};
763 # Record the page and y-position
764 $$linfo[4] = $curpage;
765 $$linfo[5] = $curypos;
766 $$linfo[6] = $curcolumn if ( defined($columnstart) );
768 if ( $curypos > $upageheight ) {
769 # We need to break the page before this line.
770 my $broken = 0; # No place found yet
771 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
772 my $linfo = $pslines[$i]->[0];
773 my $pinfo = $pslines[$i-1]->[0];
775 if ( $$linfo[1] == 2 ) {
776 # This would be an orphan, don't break.
777 } elsif ( $$linfo[1] & 1 ) {
778 # Sole line or start of paragraph. Break unless
779 # the previous line was part of a heading.
780 $broken = 1 if ( $$pinfo[0] !~ /$nobreakafter/o &&
781 $$linfo[0] !~ /$nobreakbefore/o );
783 # Middle of paragraph. Break unless we're in a
784 # no-break paragraph, or the previous line would
785 # end up being a widow.
786 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
791 die "Nowhere to break page $curpage\n" if ( !$broken );
792 # Now $i should point to line immediately before the break, i.e.
793 # the next paragraph should be the first on the new page
794 if ( defined($columnstart) &&
795 ++$curcolumn < $psconf{idxcolumns} ) {
796 # We're actually breaking text into columns, not pages
797 $curypos = $columnstart;
806 # Add end of paragraph skip
807 if ( $$linfo[1] & 2 ) {
808 $curypos += $skiparray{$$linfo[0]};
813 ps_break_pages(0,$nlines); # Break the main text body into pages
816 # Find the page number of all the indices
818 %ps_xref_page = (); # Crossref anchor pages
819 %ps_index_pages = (); # Index item pages
820 $nlines = scalar(@pslines);
821 for ( $i = 0 ; $i < $nlines ; $i++ ) {
822 my $linfo = $pslines[$i]->[0];
823 foreach my $c ( @{$pslines[$i]->[1]} ) {
824 if ( $$c[0] == -4 ) {
825 if ( !defined($ps_index_pages{$$c[1]}) ) {
826 $ps_index_pages{$$c[1]} = [];
827 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
828 # Pages are emitted in order; if this is a duplicated
829 # entry it will be the last one
832 push(@{$ps_index_pages{$$c[1]}}, $$linfo[4]);
833 } elsif ( $$c[0] == -5 ) {
834 $ps_xref_page{$$c[1]} = $$linfo[4];
840 # Emit index paragraphs
842 $startofindex = scalar(@pslines);
843 @ixparas = ([[-5,'index'],[0,'Index']]);
844 @ixptypes = ('chap');
846 foreach $k ( @ixentries ) {
848 my $ixptype = 'idx0';
849 my $prefix = $ixhasprefix{$k};
850 my @ixpara = mkparaarray($ixptype,@{$ixterms{$k}});
851 my $commapos = undef;
853 if ( defined($prefix) && $ixprefixes{$prefix} > 1 ) {
854 # This entry has a "hanging comma"
855 for ( $i = 0 ; $i < scalar(@ixpara)-1 ; $i++ ) {
856 if ( substr($ixpara[$i]->[1],-1,1) eq ',' &&
857 $ixpara[$i+1]->[1] eq ' ' ) {
863 if ( defined($commapos) ) {
864 if ( $ixcommafirst{$k} ) {
865 # This is the first entry; generate the
866 # "hanging comma" entry
867 my @precomma = splice(@ixpara,0,$commapos);
868 if ( $ixpara[0]->[1] eq ',' ) {
869 shift(@ixpara); # Discard lone comma
871 # Discard attached comma
872 $ixpara[0]->[1] =~ s/\,$//;
873 push(@precomma,shift(@ixpara));
875 push(@precomma, [-6,undef]);
876 push(@ixparas, [@precomma]);
877 push(@ixptypes, $ixptype);
878 shift(@ixpara); # Remove space
880 splice(@ixpara,0,$commapos+2);
885 push(@ixpara, [-6,undef]); # Left/right marker
886 $i = 1; $n = scalar(@{$ps_index_pages{$k}});
887 foreach $p ( @{$ps_index_pages{$k}} ) {
889 push(@ixpara,[-7,$p],[0,"$p"],[-1,undef]);
891 push(@ixpara,[-7,$p],[0,"$p,"],[-1,undef],[0,' ']);
895 push(@ixparas, [@ixpara]);
896 push(@ixptypes, $ixptype);
900 # Flow index paragraphs into lines
902 ps_break_lines(\@ixparas, \@ixptypes);
905 # Format index into pages
907 $nlines = scalar(@pslines);
908 ps_break_pages($startofindex, $nlines);
911 # Push index onto bookmark list
913 push(@bookmarks, ['index', 0, 'Index']);
915 # Get the list of fonts used
917 foreach $fset ( @AllFonts ) {
918 foreach $font ( @{$fset->{fonts}} ) {
919 $ps_all_fonts{$font->[1]->{name}}++;
923 # Emit the PostScript DSC header
924 print "%!PS-Adobe-3.0\n";
925 print "%%Pages: $curpage\n";
926 print "%%BoundingBox: 0 0 ", $psconf{pagewidth}, ' ', $psconf{pageheight}, "\n";
927 print "%%Creator: NASM psflow.pl\n";
928 print "%%DocumentData: Clean7Bit\n";
929 print "%%DocumentFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
930 print "%%DocumentNeededFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
931 print "%%Orientation: Portrait\n";
932 print "%%PageOrder: Ascend\n";
933 print "%%EndComments\n";
934 print "%%BeginProlog\n";
936 # Emit the configurables as PostScript tokens
937 foreach $c ( keys(%psconf) ) {
938 print "/$c ", $psconf{$c}, " def\n";
940 foreach $c ( keys(%psbool) ) {
941 print "/$c ", ($psbool{$c}?'true':'false'), " def\n";
944 # Emit custom encoding vector
945 $zstr = '/NASMEncoding [ ';
946 foreach $c ( @NASMEncoding ) {
947 my $z = '/'.(defined($c)?$c:'.notdef ').' ';
948 if ( length($zstr)+length($z) > 72 ) {
954 print $zstr, "] def\n";
956 # Font recoding routine
957 # newname fontname --
958 print "/nasmenc {\n";
959 print " findfont dup length dict begin\n";
960 print " { 1 index /FID ne {def}{pop pop} ifelse } forall\n";
961 print " /Encoding NASMEncoding def\n";
962 print " currentdict\n";
964 print " definefont pop\n";
967 # Emit fontset definitions
968 foreach $fset ( @AllFonts ) {
972 foreach $font ( @{$fset->{fonts}} ) {
973 $allfonts{$font->[1]->{name}}++;
975 foreach $font ( keys(%allfonts) ) {
976 print '/',$font,'-NASM /',$font," nasmenc\n";
978 foreach $font ( @{$fset->{fonts}} ) {
979 print '/', $fset->{name}, $i, ' ',
980 '/', $font->[1]->{name}, '-NASM findfont ',
981 $font->[0], " scalefont def\n";
982 push(@zfonts, $fset->{name}.$i);
985 print '/', $fset->{name}, ' [', join(' ',@zfonts), "] def\n";
988 # Emit the canned PostScript prologue
989 open(PSHEAD, "< head.ps");
990 while ( defined($line = <PSHEAD>) ) {
994 print "%%EndProlog\n";
996 # Generate a PostScript string
1001 my ($l) = length($s);
1002 for ( $i = 0 ; $i < $l ; $i++ ) {
1003 $c = substr($s,$i,1);
1004 if ( ord($c) < 32 || ord($c) > 126 ) {
1005 $o .= sprintf("\\%03o", ord($c));
1006 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
1015 # Generate PDF bookmarks
1016 print "%%BeginSetup\n";
1017 foreach $b ( @bookmarks ) {
1018 print '[/Title ', ps_string($b->[2]), "\n";
1019 print '/Count ', $b->[1], ' ' if ( $b->[1] );
1020 print '/Dest /',$b->[0]," /OUT pdfmark\n";
1023 # Ask the PostScript interpreter for the proper size media
1024 print "setpagesize\n";
1025 print "%%EndSetup\n";
1027 # Start a PostScript page
1028 sub ps_start_page() {
1030 print "%%Page: $ps_page $ps_page\n";
1031 print "%%BeginPageSetup\n";
1033 print "%%EndPageSetup\n";
1034 print '/', $ps_page, " pa\n";
1037 # End a PostScript page
1038 sub ps_end_page($) {
1041 print "($ps_page)", (($ps_page & 1) ? 'pageodd' : 'pageeven'), "\n";
1043 print "restore showpage\n";
1048 # Title page and inner cover
1050 $title = $metadata{'title'};
1051 $title =~ s/ \- / $emdash /;
1052 $pstitle = ps_string($title);
1054 # FIX THIS: This shouldn't be hard-coded like this
1056 lmarg pageheight 2 mul 3 div moveto
1058 /title linkdest ${pstitle} show
1059 lmarg pageheight 2 mul 3 div 10 sub moveto
1060 0 setlinecap 3 setlinewidth
1061 pagewidth lmarg sub rmarg sub 0 rlineto stroke
1065 /Courier-Bold findfont sz scalefont setfont
1068 [(-~~..~:\#;L .-:\#;L,.- .~:\#:;.T -~~.~:;. .~:;. )
1069 ( E8+U *T +U\' *T\# .97 *L E8+\' *;T\' *;, )
1070 ( D97 \`*L .97 \'*L \"T;E+:, D9 *L *L )
1071 ( H7 I\# T7 I\# \"*:. H7 I\# I\# )
1072 ( U: :8 *\#+ , :8 T, 79 U: :8 :8 )
1073 (,\#B. .IE, \"T;E* .IE, J *+;\#:T*\" ,\#B. .IE, .IE,)] {
1074 currentpoint 3 -1 roll
1075 sz -0.10 mul 0 3 -1 roll ashow
1076 sz 0.72 mul sub moveto
1081 pagewidth 2 div 143 sub
1082 pageheight 2 div 33 add
1089 foreach $line ( @pslines ) {
1090 my $linfo = $line->[0];
1092 if ( $$linfo[4] != $curpage ) {
1093 ps_end_page($curpage > 2);
1095 $curpage = $$linfo[4];
1100 foreach my $c ( @{$line->[1]} ) {
1101 if ( $$c[0] >= 0 ) {
1102 if ( $curfont != $$c[0] ) {
1103 print ($curfont = $$c[0]);
1105 print ps_string($$c[1]);
1106 } elsif ( $$c[0] == -1 ) {
1107 print '{el}'; # End link
1108 } elsif ( $$c[0] == -2 ) {
1109 print '{/',$$c[1],' xl}'; # xref link
1110 } elsif ( $$c[0] == -3 ) {
1111 print '{',ps_string($$c[1]),'wl}'; # web link
1112 } elsif ( $$c[0] == -4 ) {
1113 # Index anchor -- ignore
1114 } elsif ( $$c[0] == -5 ) {
1115 print '{/',$$c[1],' xa}'; #xref anchor
1116 } elsif ( $$c[0] == -6 ) {
1117 print ']['; # Start a new array
1119 } elsif ( $$c[0] == -7 ) {
1120 print '{/',$$c[1],' pl}'; # page link
1122 die "Unknown annotation";
1126 if ( defined($$linfo[2]) ) {
1127 foreach my $x ( @{$$linfo[2]} ) {
1128 if ( $$x[0] == $AuxStr ) {
1129 print ps_string($$x[1]);
1130 } elsif ( $$x[0] == $AuxPage ) {
1131 print $ps_xref_page{$$x[1]},' ';
1132 } elsif ( $$x[0] == $AuxPageStr ) {
1133 print ps_string($ps_xref_page{$$x[1]});
1134 } elsif ( $$x[0] == $AuxXRef ) {
1135 print '/',ps_xref($$x[1]),' ';
1136 } elsif ( $$x[0] == $AuxNum ) {
1139 die "Unknown auxilliary data type";
1143 print ($psconf{pageheight}-$psconf{topmarg}-$$linfo[5]);
1144 print ' ', $$linfo[6] if ( defined($$linfo[6]) );
1145 print ' ', $$linfo[0].$$linfo[1], "\n";