3 # Format the documentation as PostScript
9 require 'psfonts.ph'; # The fonts we want to use
10 require 'pswidth.ph'; # PostScript string width
15 # PostScript configurables; these values are also available to the
16 # PostScript code itself
19 pagewidth
=> 595, # Page width in PostScript points
20 pageheight
=> 792, # Page height in PostScript points
21 lmarg
=> 100, # Left margin in PostScript points
22 rmarg
=> 50, # Right margin in PostScript points
23 topmarg
=> 100, # Top margin in PostScript points
24 botmarg
=> 100, # Bottom margin in PostScript points
25 plmarg
=> 50, # Page number position relative to left margin
26 prmarg
=> 0, # Page number position relative to right margin
27 pymarg
=> 50, # Page number position relative to bot margin
28 startcopyright
=> 75, # How much above the bottom margin is the
29 # copyright notice stuff
30 bulladj
=> 12, # How much to indent a bullet paragraph
31 tocind
=> 12, # TOC indentation per level
32 tocpnz
=> 24, # Width of TOC page number only zone
33 tocdots
=> 8, # Spacing between TOC dots
34 idxspace
=> 24, # Minimum space between index title and pg#
35 idxindent
=> 24, # How much to indent a subindex entry
36 idxgutter
=> 24, # Space between index columns
37 idxcolumns
=> 2, # Number of index columns
41 colorlinks
=> 0, # Set links in blue rather than black
46 'a5' => [421, 595], # ISO half paper size
47 'b5' => [501, 709], # ISO small paper size
48 'a4' => [595, 842], # ISO standard paper size
49 'letter' => [612, 792], # US common paper size
50 'pa4' => [595, 792], # Compromise ("portable a4")
51 'b4' => [709,1002], # ISO intermediate paper size
52 'legal' => [612,1008], # US intermediate paper size
53 'a3' => [842,1190], # ISO double paper size
54 '11x17' => [792,1224], # US double paper size
58 # Parse the command line
61 while ( $arg = shift(@ARGV) ) {
62 if ( $arg =~ /^\-(|no\-)(.*)$/ ) {
64 $true = ($1 eq '') ?
1 : 0;
65 if ( $true && defined($papersizes{$parm}) ) {
66 $psconf{pagewidth
} = $papersizes{$parm}->[0];
67 $psconf{pageheight
} = $papersizes{$parm}->[1];
68 } elsif ( defined($psbool{$parm}) ) {
69 $psbool{$parm} = $true;
70 } elsif ( $true && defined($psconf{$parm}) ) {
71 $psconf{$parm} = shift(@ARGV);
72 } elsif ( $parm =~ /^(title|subtitle|year|author|license)$/ ) {
73 $metadata{$parm} = shift(@ARGV);
75 die "$0: Unknown option: $arg\n";
83 # Document formatting parameters
85 $paraskip = 6; # Space between paragraphs
86 $chapstart = 30; # Space before a chapter heading
87 $chapskip = 24; # Space after a chapter heading
88 $tocskip = 6; # Space between TOC entries
90 # Configure post-paragraph skips for each kind of paragraph
91 %skiparray = ('chap' => $chapskip, 'appn' => $chapstart,
92 'head' => $paraskip, 'subh' => $paraskip,
93 'norm' => $paraskip, 'bull' => $paraskip,
94 'code' => $paraskip, 'toc0' => $tocskip,
95 'toc1' => $tocskip, 'toc2' => $tocskip);
97 # Custom encoding vector. This is basically the same as
98 # ISOLatin1Encoding (a level 2 feature, so we dont want to use it),
99 # but with the "naked" accents at \200-\237 moved to the \000-\037
100 # range (ASCII control characters), and a few extra characters thrown
101 # in. It is basically a modified Windows 1252 codepage, minus, for
102 # now, the euro sign (\200 is reserved for euro.)
106 undef, undef, undef, undef, undef, undef, undef, undef, undef, undef,
107 undef, undef, undef, undef, undef, undef, 'dotlessi', 'grave',
108 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
109 'dieresis', undef, 'ring', 'cedilla', undef, 'hungarumlaut',
110 'ogonek', 'caron', 'space', 'exclam', 'quotedbl', 'numbersign',
111 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft',
112 'parenright', 'asterisk', 'plus', 'comma', 'minus', 'period',
113 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six',
114 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal',
115 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
116 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
117 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
118 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e',
119 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
120 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
121 'asciitilde', undef, undef, undef, 'quotesinglbase', 'florin',
122 'quotedblbase', 'ellipsis', 'dagger', 'dbldagger', 'circumflex',
123 'perthousand', 'Scaron', 'guilsinglleft', 'OE', undef, 'Zcaron',
124 undef, undef, 'grave', 'quotesingle', 'quotedblleft',
125 'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark',
126 'scaron', 'guilsignlright', 'oe', undef, 'zcaron', 'Ydieresis',
127 'space', 'exclamdown', 'cent', 'sterling', 'currency', 'yen',
128 'brokenbar', 'section', 'dieresis', 'copyright', 'ordfeminine',
129 'guillemotleft', 'logicalnot', 'hyphen', 'registered', 'macron',
130 'degree', 'plusminus', 'twosuperior', 'threesuperior', 'acute', 'mu',
131 'paragraph', 'periodcentered', 'cedilla', 'onesuperior',
132 'ordmasculine', 'guillemotright', 'onequarter', 'onehalf',
133 'threequarters', 'questiondown', 'Agrave', 'Aacute', 'Acircumflex',
134 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla', 'Egrave', 'Eacute',
135 'Ecircumflex', 'Edieresis', 'Igrave', 'Iacute', 'Icircumflex',
136 'Idieresis', 'Eth', 'Ntilde', 'Ograve', 'Oacute', 'Ocircumflex',
137 'Otilde', 'Odieresis', 'multiply', 'Oslash', 'Ugrave', 'Uacute',
138 'Ucircumflex', 'Udieresis', 'Yacute', 'Thorn', 'germandbls',
139 'agrave', 'aacute', 'acircumflex', 'atilde', 'adieresis', 'aring',
140 'ae', 'ccedilla', 'egrave', 'eacute', 'ecircumflex', 'edieresis',
141 'igrave', 'iacute', 'icircumflex', 'idieresis', 'eth', 'ntilde',
142 'ograve', 'oacute', 'ocircumflex', 'otilde', 'odieresis', 'divide',
143 'oslash', 'ugrave', 'uacute', 'ucircumflex', 'udieresis', 'yacute',
147 # Name-to-byte lookup hash
149 for ( $i = 0 ; $i < 256 ; $i++ ) {
150 $charcode{$NASMEncoding[$i]} = chr($i);
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 $s =~ s/\B\-\-\B/$charcode{'emdash'}/g;
221 $s =~ s/\B\-\B/ $charcode{'endash'} /g;
223 while ( $s =~ /^(\s+|\S+)(.*)$/ ) {
232 # Take a crossreference name and generate the PostScript name for it.
234 # This hack produces a somewhat smaller PDF...
239 # my $q = $ps_xref_list{$s};
240 # return $q if ( defined($ps_xref_list{$s}) );
241 # $q = 'X'.int2base($ps_xref_next++, 52);
242 # $ps_xref_list{$s} = $q;
246 # Somewhat bigger PDF, but one which obeys # URLs
252 # Flow lines according to a particular font set and width
254 # A "font set" is represented as an array containing
255 # arrays of pairs: [<size>, <metricref>]
257 # Each line is represented as:
258 # [ [type,first|last,aux,fontset,page,ypos,optional col],
259 # [rendering array] ]
261 # A space character may be "squeezed" by up to this much
262 # (as a fraction of the normal width of a space.)
264 $ps_space_squeeze = 0.00; # Min space width 100%
265 sub ps_flow_lines
($$$@
) {
266 my($wid, $fontset, $type, @data) = @_;
267 my($fonts) = $$fontset{fonts
};
269 my($w) = 0; # Width of current line
270 my($sw) = 0; # Width of current line due to spaces
271 my(@l) = (); # Current line
272 my(@ls) = (); # Accumulated output lines
273 my(@xd) = (); # Metadata that goes with subsequent text
274 my $hasmarker = 0; # Line has -6 marker
275 my $pastmarker = 0; # -6 marker found
277 # If there is a -6 marker anywhere in the paragraph,
278 # *each line* output needs to have a -6 marker
279 foreach $e ( @data ) {
280 $hasmarker = 1 if ( $$e[0] == -6 );
284 foreach $e ( @data ) {
286 # Type is metadata. Zero width.
287 if ( $$e[0] == -6 ) {
290 if ( $$e[0] == -1 || $$e[0] == -6 ) {
291 # -1 (end anchor) or -6 (marker) goes with the preceeding
292 # text, otherwise with the subsequent text
298 my $ew = ps_width
($$e[1], $fontset->{fonts
}->[$$e[0]][1],
300 ($fontset->{fonts
}->[$$e[0]][0]/1000);
302 $sp =~ tr/[^ ]//d; # Delete nonspaces
303 my $esw = ps_width
($sp, $fontset->{fonts
}->[$$e[0]][1],
305 ($fontset->{fonts
}->[$$e[0]][0]/1000);
307 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
309 # Search backwards for previous space chunk
310 my $lx = scalar(@l)-1;
313 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) {
315 $pastmarker = 0 if ( $l[$lx]->[0] == -6 );
319 if ( $l[$lx]->[1] eq ' ' ) {
321 @rm = splice(@l, $lx);
322 last; # Found place to break
329 # Now @l contains the stuff to remain on the old line
330 # If we broke the line inside a link, then split the link
333 foreach my $lc ( @l ) {
334 if ( $$lc[0] == -2 || $$lc[0] == -3 || $lc[0] == -7 ) {
336 } elsif ( $$lc[0] == -1 ) {
341 if ( defined($lkref) ) {
342 push(@l, [-1,undef]); # Terminate old reference
343 unshift(@rm, $lkref); # Duplicate reference on new line
348 unshift(@rm,[-6,undef]); # New line starts with marker
350 push(@l,[-6,undef]); # Old line ends with marker
354 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
358 # Compute the width of the remainder array
360 if ( $$le[0] >= 0 ) {
361 my $xew = ps_width
($$le[1],
362 $fontset->{fonts
}->[$$le[0]][1],
364 ($fontset->{fonts
}->[$$le[0]][0]/1000);
366 $xsp =~ tr/[^ ]//d; # Delete nonspaces
367 my $xsw = ps_width
($xsp,
368 $fontset->{fonts
}->[$$le[0]][1],
370 ($fontset->{fonts
}->[$$le[0]][0]/1000);
371 $w += $xew; $sw += $xsw;
375 push(@l, @xd); # Accumulated metadata
377 if ( $$e[1] ne '' ) {
379 $w += $ew; $sw += $esw;
385 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
388 # Mark the first line as first and the last line as last
390 $ls[0]->[0]->[1] |= 1; # First in para
391 $ls[-1]->[0]->[1] |= 2; # Last in para
397 # Once we have broken things into lines, having multiple chunks
398 # with the same font index is no longer meaningful. Merge
399 # adjacent chunks to keep down the size of the whole file.
401 sub ps_merge_chunks
(@
) {
408 $eco = -1; # Index of the last entry in @co
410 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
411 $co[$eco]->[1] .= $$c[1];
413 push(@co, $c); $eco++;
421 # Convert paragraphs to rendering arrays. Each
422 # element in the array contains (font, string),
423 # where font can be one of:
427 # -4 index item anchor
429 # -6 left/right marker (used in the index)
430 # -7 page link (used in the index)
433 # 2 code (fixed spacing)
436 sub mkparaarray
($@
) {
437 my($ptype, @chunks) = @_;
443 if ( $ptype =~ /^code/ ) {
444 foreach $chunk ( @chunks ) {
445 push(@para, [2, $chunk]);
448 foreach $chunk ( @chunks ) {
449 my $type = substr($chunk,0,2);
450 my $text = substr($chunk,2);
452 if ( $type eq 'sp' ) {
453 push(@para, [$in_e?
1:0, ' ']);
454 } elsif ( $type eq 'da' ) {
455 push(@para, [$in_e?
1:0, $charcode{'endash'}]);
456 } elsif ( $type eq 'n ' ) {
457 push(@para, [0, $text]);
459 } elsif ( $type =~ '^e' ) {
460 push(@para, [1, $text]);
461 $in_e = ($type eq 'es' || $type eq 'e ');
462 } elsif ( $type eq 'c ' ) {
463 push(@para, [2, $text]);
465 } elsif ( $type eq 'x ' ) {
466 push(@para, [-2, ps_xref
($text)]);
467 } elsif ( $type eq 'xe' ) {
468 push(@para, [-1, undef]);
469 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
470 $text =~ /\<(.*)\>(.*)$/;
471 my $link = $1; $text = $2;
472 push(@para, [-3, $link]);
473 push(@para, [($type eq 'wc') ?
2:0, $text]);
474 push(@para, [-1, undef]);
476 } elsif ( $type eq 'i ' ) {
477 push(@para, [-4, $text]);
479 die "Unexpected paragraph chunk: $chunk";
486 $npara = scalar(@paras);
487 for ( $i = 0 ; $i < $npara ; $i++ ) {
488 $paras[$i] = [mkparaarray
($ptypes[$i], @
{$paras[$i]})];
492 # This converts a rendering array to a simple string
494 sub ps_arraytostr
(@
) {
498 $s .= $$c[1] if ( $$c[0] >= 0 );
504 # This generates a duplicate of a paragraph
519 # This generates a duplicate of a paragraph, stripping anchor
522 sub ps_dup_para_noanchor
(@
) {
529 push(@o, [@cc]) unless ( $cc[0] == -4 || $cc[0] == -5 );
535 # Scan for header paragraphs and fix up their contents;
536 # also generate table of contents and PDF bookmarks.
538 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
539 @tocptypes = ('chap');
540 @bookmarks = (['title', 0, 'Title'], ['contents', 0, 'Contents']);
542 for ( $i = 0 ; $i < $npara ; $i++ ) {
543 my $xtype = $ptypes[$i];
544 my $ptype = substr($xtype,0,4);
548 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
549 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
554 my $xref = ps_xref
($sech);
555 my $chap = ($ptype eq 'chap')?
'Chapter':'Appendix';
557 $book = [$xref, 0, ps_arraytostr
(@
{$paras[$i]})];
558 push(@bookmarks, $book);
559 $bookref{$secn} = $book;
561 push(@tocparas, [ps_dup_para_noanchor
(@
{$paras[$i]})]);
562 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
564 unshift(@
{$paras[$i]},
565 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
566 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
567 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
572 my $xref = ps_xref
($sech);
574 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
576 $book = [$xref, 0, ps_arraytostr
(@
{$paras[$i]})];
577 push(@bookmarks, $book);
578 $bookref{$secn} = $book;
579 $bookref{$pref}->[1]--; # Adjust count for parent node
581 push(@tocparas, [ps_dup_para_noanchor
(@
{$paras[$i]})]);
583 (($ptype eq 'subh') ?
'toc2':'toc1').' :'.$sech.':'.$secn);
585 unshift(@
{$paras[$i]}, [-5, $xref]);
590 # Add TOC to beginning of paragraph list
592 unshift(@paras, @tocparas); undef @tocparas;
593 unshift(@ptypes, @tocptypes); undef @tocptypes;
596 # Add copyright notice to the beginning
599 ([[0, $charcode{'copyright'}],
600 [0, ' '], [0, $metadata{'year'}],
601 [0, ' '], string2array
($metadata{'author'}),
602 [0, ' '], string2array
($metadata{'copyright_tail'})],
603 [string2array
($metadata{'license'})],
604 [string2array
($metadata{'auxinfo'})]);
606 unshift(@paras, @copyright_page);
607 unshift(@ptypes, ('norm') x
scalar(@copyright_page));
609 $npara = scalar(@paras);
612 # No lines generated, yet.
617 # Line Auxilliary Information Types
619 $AuxStr = 1; # String
620 $AuxPage = 2; # Page number (from xref)
621 $AuxPageStr = 3; # Page number as a PostScript string
622 $AuxXRef = 4; # Cross reference as a name
623 $AuxNum = 5; # Number
626 # Break or convert paragraphs into lines, and push them
627 # onto the @pslines array.
629 sub ps_break_lines
($$) {
630 my ($paras,$ptypes) = @_;
632 my $linewidth = $psconf{pagewidth
}-$psconf{lmarg
}-$psconf{rmarg
};
633 my $bullwidth = $linewidth-$psconf{bulladj
};
634 my $indxwidth = ($linewidth-$psconf{idxgutter
})/$psconf{idxcolumns
}
637 my $npara = scalar(@
{$paras});
640 for ( $i = 0 ; $i < $npara ; $i++ ) {
641 my $xtype = $ptypes->[$i];
642 my $ptype = substr($xtype,0,4);
643 my @data = @
{$paras->[$i]};
645 if ( $ptype eq 'code' ) {
647 # Code paragraph; each chunk is a line
648 foreach $p ( @data ) {
649 push(@ls, [[$ptype,0,undef,\
%BodyFont,0,0],[$p]]);
651 $ls[0]->[0]->[1] |= 1; # First in para
652 $ls[-1]->[0]->[1] |= 2; # Last in para
653 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
654 # Chapters are flowed normally, but in an unusual font
655 @ls = ps_flow_lines
($linewidth, \
%ChapFont, $ptype, @data);
656 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
657 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
662 my $font = ($ptype eq 'head') ? \
%HeadFont : \
%SubhFont;
663 @ls = ps_flow_lines
($linewidth, $font, $ptype, @data);
664 # We need the heading number as auxillary data
665 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
666 } elsif ( $ptype eq 'norm' ) {
667 @ls = ps_flow_lines
($linewidth, \
%BodyFont, $ptype, @data);
668 } elsif ( $ptype eq 'bull' ) {
669 @ls = ps_flow_lines
($bullwidth, \
%BodyFont, $ptype, @data);
670 } elsif ( $ptype =~ /^toc/ ) {
671 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
675 my $refname = $2.' ';
676 my $ntoc = substr($ptype,3,1)+0;
677 my $refwidth = ps_width
($refname, $BodyFont{fonts
}->[0][1],
679 ($BodyFont{fonts
}->[0][0]/1000);
681 @ls = ps_flow_lines
($linewidth-$ntoc*$psconf{tocind
}-
682 $psconf{tocpnz
}-$refwidth,
683 \
%BodyFont, $ptype, @data);
685 # Auxilliary data: for the first line, the cross reference symbol
686 # and the reference name; for all lines but the first, the
687 # reference width; and for the last line, the page number
689 my $nl = scalar(@ls);
690 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
691 for ( $j = 1 ; $j < $nl ; $j++ ) {
692 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
694 push(@
{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
695 } elsif ( $ptype =~ /^idx/ ) {
696 my $lvl = substr($ptype,3,1)+0;
698 @ls = ps_flow_lines
($indxwidth-$lvl*$psconf{idxindent
},
699 \
%BodyFont, $ptype, @data);
701 die "Unknown para type: $ptype";
703 # Merge adjacent identical chunks
705 @
{$$l[1]} = ps_merge_chunks
(@
{$$l[1]});
711 # Break the main body text into lines.
712 ps_break_lines
(\
@paras, \
@ptypes);
715 # Break lines in to pages
718 # Where to start on page 2, the copyright page
719 $curpage = 2; # Start on page 2
720 $curypos = $psconf{pageheight
}-$psconf{topmarg
}-$psconf{botmarg
}-
721 $psconf{startcopyright
};
722 undef $columnstart; # Not outputting columnar text
723 undef $curcolumn; # Current column
724 $nlines = scalar(@pslines);
727 # This formats lines inside the global @pslines array into pages,
728 # updating the page and y-coordinate entries. Start at the
729 # $startline position in @pslines and go to but not including
730 # $endline. The global variables $curpage, $curypos, $columnstart
731 # and $curcolumn are updated appropriately.
733 sub ps_break_pages
($$) {
734 my($startline, $endline) = @_;
736 # Paragraph types which should never be broken
737 my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
738 # Paragraph types which are heading (meaning they should not be broken
740 my $nobreakafter = "^(chap|appn|head|subh)\$";
741 # Paragraph types which should never be broken *before*
742 my $nobreakbefore = "^idx[1-9]\$";
743 # Paragraph types which are set in columnar format
744 my $columnregexp = "^idx.\$";
746 my $upageheight = $psconf{pageheight
}-$psconf{topmarg
}-$psconf{botmarg
};
750 for ( $i = $startline ; $i < $endline ; $i++ ) {
751 my $linfo = $pslines[$i]->[0];
752 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
753 && ($$linfo[1] & 1) ) {
754 # First line of a new chapter heading. Start a new page.
756 $curpage++ if ( $curypos > 0 || defined($columnstart) );
757 $curypos = $chapstart;
758 } elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
764 if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
765 $columnstart = $curypos;
769 # Adjust position by the appropriate leading
770 $curypos += $$linfo[3]->{leading
};
772 # Record the page and y-position
773 $$linfo[4] = $curpage;
774 $$linfo[5] = $curypos;
775 $$linfo[6] = $curcolumn if ( defined($columnstart) );
777 if ( $curypos > $upageheight ) {
778 # We need to break the page before this line.
779 my $broken = 0; # No place found yet
780 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
781 my $linfo = $pslines[$i]->[0];
782 my $pinfo = $pslines[$i-1]->[0];
784 if ( $$linfo[1] == 2 ) {
785 # This would be an orphan, don't break.
786 } elsif ( $$linfo[1] & 1 ) {
787 # Sole line or start of paragraph. Break unless
788 # the previous line was part of a heading.
789 $broken = 1 if ( $$pinfo[0] !~ /$nobreakafter/o &&
790 $$linfo[0] !~ /$nobreakbefore/o );
792 # Middle of paragraph. Break unless we're in a
793 # no-break paragraph, or the previous line would
794 # end up being a widow.
795 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
800 die "Nowhere to break page $curpage\n" if ( !$broken );
801 # Now $i should point to line immediately before the break, i.e.
802 # the next paragraph should be the first on the new page
803 if ( defined($columnstart) &&
804 ++$curcolumn < $psconf{idxcolumns
} ) {
805 # We're actually breaking text into columns, not pages
806 $curypos = $columnstart;
815 # Add end of paragraph skip
816 if ( $$linfo[1] & 2 ) {
817 $curypos += $skiparray{$$linfo[0]};
822 ps_break_pages
(0,$nlines); # Break the main text body into pages
825 # Find the page number of all the indices
827 %ps_xref_page = (); # Crossref anchor pages
828 %ps_index_pages = (); # Index item pages
829 $nlines = scalar(@pslines);
830 for ( $i = 0 ; $i < $nlines ; $i++ ) {
831 my $linfo = $pslines[$i]->[0];
832 foreach my $c ( @
{$pslines[$i]->[1]} ) {
833 if ( $$c[0] == -4 ) {
834 if ( !defined($ps_index_pages{$$c[1]}) ) {
835 $ps_index_pages{$$c[1]} = [];
836 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
837 # Pages are emitted in order; if this is a duplicated
838 # entry it will be the last one
841 push(@
{$ps_index_pages{$$c[1]}}, $$linfo[4]);
842 } elsif ( $$c[0] == -5 ) {
843 $ps_xref_page{$$c[1]} = $$linfo[4];
849 # Emit index paragraphs
851 $startofindex = scalar(@pslines);
852 @ixparas = ([[-5,'index'],[0,'Index']]);
853 @ixptypes = ('chap');
855 foreach $k ( @ixentries ) {
857 my $ixptype = 'idx0';
858 my $prefix = $ixhasprefix{$k};
859 my @ixpara = mkparaarray
($ixptype,@
{$ixterms{$k}});
860 my $commapos = undef;
862 if ( defined($prefix) && $ixprefixes{$prefix} > 1 ) {
863 # This entry has a "hanging comma"
864 for ( $i = 0 ; $i < scalar(@ixpara)-1 ; $i++ ) {
865 if ( substr($ixpara[$i]->[1],-1,1) eq ',' &&
866 $ixpara[$i+1]->[1] eq ' ' ) {
872 if ( defined($commapos) ) {
873 if ( $ixcommafirst{$k} ) {
874 # This is the first entry; generate the
875 # "hanging comma" entry
876 my @precomma = splice(@ixpara,0,$commapos);
877 if ( $ixpara[0]->[1] eq ',' ) {
878 shift(@ixpara); # Discard lone comma
880 # Discard attached comma
881 $ixpara[0]->[1] =~ s/\,$//;
882 push(@precomma,shift(@ixpara));
884 push(@precomma, [-6,undef]);
885 push(@ixparas, [@precomma]);
886 push(@ixptypes, $ixptype);
887 shift(@ixpara); # Remove space
889 splice(@ixpara,0,$commapos+2);
894 push(@ixpara, [-6,undef]); # Left/right marker
895 $i = 1; $n = scalar(@
{$ps_index_pages{$k}});
896 foreach $p ( @
{$ps_index_pages{$k}} ) {
898 push(@ixpara,[-7,$p],[0,"$p"],[-1,undef]);
900 push(@ixpara,[-7,$p],[0,"$p,"],[-1,undef],[0,' ']);
904 push(@ixparas, [@ixpara]);
905 push(@ixptypes, $ixptype);
909 # Flow index paragraphs into lines
911 ps_break_lines
(\
@ixparas, \
@ixptypes);
914 # Format index into pages
916 $nlines = scalar(@pslines);
917 ps_break_pages
($startofindex, $nlines);
920 # Push index onto bookmark list
922 push(@bookmarks, ['index', 0, 'Index']);
924 # Get the list of fonts used
926 foreach $fset ( @AllFonts ) {
927 foreach $font ( @
{$fset->{fonts
}} ) {
928 $ps_all_fonts{$font->[1]->{name
}}++;
932 # Emit the PostScript DSC header
933 print "%!PS-Adobe-3.0\n";
934 print "%%Pages: $curpage\n";
935 print "%%BoundingBox: 0 0 ", $psconf{pagewidth
}, ' ', $psconf{pageheight
}, "\n";
936 print "%%Creator: (NASM psflow.pl)\n";
937 print "%%DocumentData: Clean7Bit\n";
938 print "%%DocumentFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
939 print "%%DocumentNeededFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
940 print "%%Orientation: Portrait\n";
941 print "%%PageOrder: Ascend\n";
942 print "%%EndComments\n";
943 print "%%BeginProlog\n";
945 # Emit the configurables as PostScript tokens
946 foreach $c ( keys(%psconf) ) {
947 print "/$c ", $psconf{$c}, " def\n";
949 foreach $c ( keys(%psbool) ) {
950 print "/$c ", ($psbool{$c}?
'true':'false'), " def\n";
953 # Emit custom encoding vector
954 $zstr = '/NASMEncoding [ ';
955 foreach $c ( @NASMEncoding ) {
956 my $z = '/'.(defined($c)?
$c:'.notdef ').' ';
957 if ( length($zstr)+length($z) > 72 ) {
963 print $zstr, "] def\n";
965 # Font recoding routine
966 # newname fontname --
967 print "/nasmenc {\n";
968 print " findfont dup length dict begin\n";
969 print " { 1 index /FID ne {def}{pop pop} ifelse } forall\n";
970 print " /Encoding NASMEncoding def\n";
971 print " currentdict\n";
973 print " definefont pop\n";
976 # Emit fontset definitions
977 foreach $font ( keys(%ps_all_fonts) ) {
978 print '/',$font,'-NASM /',$font," nasmenc\n";
981 foreach $fset ( @AllFonts ) {
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 # This is used by the bullet-paragraph PostScript methods
995 print "/bullet [",ps_string
($charcode{'bullet'}),"] def\n";
997 # Emit the canned PostScript prologue
998 open(PSHEAD
, "< head.ps");
999 while ( defined($line = <PSHEAD
>) ) {
1003 print "%%EndProlog\n";
1005 # Generate a PostScript string
1010 my ($l) = length($s);
1011 for ( $i = 0 ; $i < $l ; $i++ ) {
1012 $c = substr($s,$i,1);
1013 if ( ord($c) < 32 || ord($c) > 126 ) {
1014 $o .= sprintf("\\%03o", ord($c));
1015 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
1024 # Generate PDF bookmarks
1025 print "%%BeginSetup\n";
1026 foreach $b ( @bookmarks ) {
1027 print '[/Title ', ps_string
($b->[2]), "\n";
1028 print '/Count ', $b->[1], ' ' if ( $b->[1] );
1029 print '/Dest /',$b->[0]," /OUT pdfmark\n";
1032 # Ask the PostScript interpreter for the proper size media
1033 print "setpagesize\n";
1034 print "%%EndSetup\n";
1036 # Start a PostScript page
1037 sub ps_start_page
() {
1039 print "%%Page: $ps_page $ps_page\n";
1040 print "%%BeginPageSetup\n";
1042 print "%%EndPageSetup\n";
1043 print '/', $ps_page, " pa\n";
1046 # End a PostScript page
1047 sub ps_end_page
($) {
1050 print "($ps_page)", (($ps_page & 1) ?
'pageodd' : 'pageeven'), "\n";
1052 print "restore showpage\n";
1059 $title = $metadata{'title'} || '';
1060 $title =~ s/ \- / $charcode{'emdash'} /;
1062 $subtitle = $metadata{'subtitle'} || '';
1063 $subtitle =~ s/ \- / $charcode{'emdash'} /;
1066 print "/ti ", ps_string
($title), " def\n";
1067 print "/sti ", ps_string
($subtitle), " def\n";
1068 print "lmarg pageheight 2 mul 3 div moveto\n";
1069 print "tfont0 setfont\n";
1070 print "/title linkdest ti show\n";
1071 print "lmarg pageheight 2 mul 3 div 10 sub moveto\n";
1072 print "0 setlinecap 3 setlinewidth\n";
1073 print "pagewidth lmarg sub rmarg sub 0 rlineto currentpoint stroke moveto\n";
1074 print "hfont1 setfont sti stringwidth pop neg ",
1075 -$HeadFont{leading
}, " rmoveto\n";
1078 # Print logo, if there is one
1079 # FIX: To be 100% correct, this should look for DocumentNeeded*
1080 # and DocumentFonts in the header of the EPSF and add those to the
1082 if ( defined($metadata{epslogo
}) &&
1083 sysopen(EPS
, $metadata{epslogo
}, O_RDONLY
) ) {
1085 my ($bbllx,$bblly,$bburx,$bbury) = (undef,undef,undef,undef);
1088 my $maxwidth = $psconf{pagewidth
}-$psconf{lmarg
}-$psconf{rmarg
};
1089 my $maxheight = $psconf{pageheight
}/3-40;
1093 while ( defined($line = <EPS
>) ) {
1094 last if ( $line =~ /^%%EOF/ );
1095 if ( !defined($bbllx) &&
1096 $line =~ /^\%\%BoundingBox\:\s*([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/i ) {
1097 $bbllx = $1+0; $bblly = $2+0;
1098 $bburx = $3+0; $bbury = $4+0;
1104 if ( defined($bbllx) ) {
1105 $width = $bburx-$bbllx;
1106 $height = $bbury-$bblly;
1108 if ( $width > $maxwidth ) {
1109 $scale = $maxwidth/$width;
1111 if ( $height*$scale > $maxheight ) {
1112 $scale = $maxheight/$height;
1115 $x = ($psconf{pagewidth
}-$width*$scale)/2;
1116 $y = ($psconf{pageheight
}-$height*$scale)/2;
1118 print "BeginEPSF\n";
1119 print $x, ' ', $y, " translate\n";
1120 print $scale, " dup scale\n" unless ( $scale == 1 );
1121 print -$bbllx, ' ', -$bblly, " translate\n";
1122 print "$bbllx $bblly moveto\n";
1123 print "$bburx $bblly lineto\n";
1124 print "$bburx $bbury lineto\n";
1125 print "$bbllx $bbury lineto\n";
1126 print "$bbllx $bblly lineto clip newpath\n";
1127 print "%%BeginDocument: ",ps_string
($metadata{epslogo
}),"\n";
1129 print "%%EndDocument\n";
1135 # Emit the rest of the document (page 2 and on)
1138 foreach $line ( @pslines ) {
1139 my $linfo = $line->[0];
1141 if ( $$linfo[4] != $curpage ) {
1142 ps_end_page
($curpage > 2);
1144 $curpage = $$linfo[4];
1149 foreach my $c ( @
{$line->[1]} ) {
1150 if ( $$c[0] >= 0 ) {
1151 if ( $curfont != $$c[0] ) {
1152 print ($curfont = $$c[0]);
1154 print ps_string
($$c[1]);
1155 } elsif ( $$c[0] == -1 ) {
1156 print '{el}'; # End link
1157 } elsif ( $$c[0] == -2 ) {
1158 print '{/',$$c[1],' xl}'; # xref link
1159 } elsif ( $$c[0] == -3 ) {
1160 print '{',ps_string
($$c[1]),'wl}'; # web link
1161 } elsif ( $$c[0] == -4 ) {
1162 # Index anchor -- ignore
1163 } elsif ( $$c[0] == -5 ) {
1164 print '{/',$$c[1],' xa}'; #xref anchor
1165 } elsif ( $$c[0] == -6 ) {
1166 print ']['; # Start a new array
1168 } elsif ( $$c[0] == -7 ) {
1169 print '{/',$$c[1],' pl}'; # page link
1171 die "Unknown annotation";
1175 if ( defined($$linfo[2]) ) {
1176 foreach my $x ( @
{$$linfo[2]} ) {
1177 if ( $$x[0] == $AuxStr ) {
1178 print ps_string
($$x[1]);
1179 } elsif ( $$x[0] == $AuxPage ) {
1180 print $ps_xref_page{$$x[1]},' ';
1181 } elsif ( $$x[0] == $AuxPageStr ) {
1182 print ps_string
($ps_xref_page{$$x[1]});
1183 } elsif ( $$x[0] == $AuxXRef ) {
1184 print '/',ps_xref
($$x[1]),' ';
1185 } elsif ( $$x[0] == $AuxNum ) {
1188 die "Unknown auxilliary data type";
1192 print ($psconf{pageheight
}-$psconf{topmarg
}-$$linfo[5]);
1193 print ' ', $$linfo[6] if ( defined($$linfo[6]) );
1194 print ' ', $$linfo[0].$$linfo[1], "\n";