2 ## --------------------------------------------------------------------------
4 ## Copyright 1996-2017 The NASM Authors - All Rights Reserved
5 ## See the file AUTHORS included with the NASM distribution for
6 ## the specific copyright holders.
8 ## Redistribution and use in source and binary forms, with or without
9 ## modification, are permitted provided that the following
10 ## conditions are met:
12 ## * Redistributions of source code must retain the above copyright
13 ## notice, this list of conditions and the following disclaimer.
14 ## * Redistributions in binary form must reproduce the above
15 ## copyright notice, this list of conditions and the following
16 ## disclaimer in the documentation and/or other materials provided
17 ## with the distribution.
19 ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
20 ## CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
21 ## INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
22 ## MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 ## DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 ## CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ## NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 ## LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 ## HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 ## CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 ## OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
31 ## EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ## --------------------------------------------------------------------------
36 # Format the documentation as PostScript
41 require 'psfonts.ph'; # The fonts we want to use
42 require 'pswidth.ph'; # PostScript string width
43 require 'findfont.ph'; # Find fonts in the system
46 # Document formatting parameters
49 pagewidth
=> 595, # Page width in PostScript points
50 pageheight
=> 792, # Page height in PostScript points
51 lmarg
=> 72*1.25, # Left margin in PostScript points
52 rmarg
=> 72, # Right margin in PostScript points
53 topmarg
=> 72, # Top margin in PostScript points
54 botmarg
=> 72, # Bottom margin in PostScript points
55 plmarg
=> 72*0.25, # Page number position relative to left margin
56 prmarg
=> 0, # Page number position relative to right margin
57 pymarg
=> 24, # Page number position relative to bot margin
58 startcopyright
=> 75, # How much above the bottom margin is the
59 # copyright notice stuff
60 bulladj
=> 12, # How much to indent a bullet/indented paragraph
61 tocind
=> 12, # TOC indentation per level
62 tocpnz
=> 24, # Width of TOC page number only zone
63 tocdots
=> 8, # Spacing between TOC dots
64 idxspace
=> 24, # Minimum space between index title and pg#
65 idxindent
=> 24, # How much to indent a subindex entry
66 idxgutter
=> 24, # Space between index columns
67 idxcolumns
=> 2, # Number of index columns
69 paraskip
=> 6, # Space between paragraphs
70 chapstart
=> 30, # Space before a chapter heading
71 chapskip
=> 24, # Space after a chapter heading
72 tocskip
=> 6, # Space between TOC entries
76 colorlinks
=> 0, # Set links in blue rather than black
81 'a5' => [421, 595], # ISO half paper size
82 'b5' => [501, 709], # ISO small paper size
83 'a4' => [595, 842], # ISO standard paper size
84 'letter' => [612, 792], # US common paper size
85 'pa4' => [595, 792], # Compromise ("portable a4")
86 'b4' => [709,1002], # ISO intermediate paper size
87 'legal' => [612,1008], # US intermediate paper size
88 'a3' => [842,1190], # ISO double paper size
89 '11x17' => [792,1224], # US double paper size
97 $epsdir = File
::Spec
->curdir();
100 # Parse the command line
103 while ( $arg = shift(@ARGV) ) {
104 if ( $arg =~ /^\-(|no\-)(.*)$/ ) {
106 $true = ($1 eq '') ?
1 : 0;
107 if ( $true && defined($papersizes{$parm}) ) {
108 $psconf{pagewidth
} = $papersizes{$parm}->[0];
109 $psconf{pageheight
} = $papersizes{$parm}->[1];
110 } elsif ( defined($psbool{$parm}) ) {
111 $psbool{$parm} = $true;
112 } elsif ( $true && defined($psconf{$parm}) ) {
113 $psconf{$parm} = shift(@ARGV);
114 } elsif ( $true && $parm =~ /^(title|subtitle|year|author|license)$/ ) {
115 $metadata{$parm} = shift(@ARGV);
116 } elsif ( $true && $parm eq 'fontsdir' ) {
117 $fontsdir = shift(@ARGV);
118 } elsif ( $true && $parm eq 'epsdir' ) {
119 $epsdir = shift(@ARGV);
120 } elsif ( $true && $parm eq 'headps' ) {
121 $headps = shift(@ARGV);
123 die "$0: Unknown option: $arg\n";
130 # Configure post-paragraph skips for each kind of paragraph
131 # (subject to modification above)
132 %skiparray = ('chap' => $psconf{chapskip
},
133 'appn' => $psconf{chapstart
},
134 'head' => $psconf{paraskip
},
135 'subh' => $psconf{paraskip
},
136 'norm' => $psconf{paraskip
},
137 'bull' => $psconf{paraskip
},
138 'indt' => $psconf{paraskip
},
139 'bquo' => $psconf{paraskip
},
140 'code' => $psconf{paraskip
},
141 'toc0' => $psconf{tocskip
},
142 'toc1' => $psconf{tocskip
},
143 'toc2' => $psconf{tocskip
}
146 # Read the font metrics files, and update @AllFonts
147 # Get the list of fonts used
150 foreach my $fset ( @AllFonts ) {
151 foreach my $font ( @
{$fset->{fonts
}} ) {
153 my @flist = @
{$font->[1]};
155 while (defined($fname = shift(@flist))) {
156 $fdata = findfont
($fname);
157 last if (defined($fdata));
159 if (!defined($fdata)) {
160 die "$infile: no font found of: ".
161 join(', ', @
{$font->[1]}), "\n".
162 "Install one of these fonts or update psfonts.ph\n";
164 $ps_all_fonts{$fname} = $fdata;
169 # Custom encoding vector. This is basically the same as
170 # ISOLatin1Encoding (a level 2 feature, so we dont want to use it),
171 # but with the "naked" accents at \200-\237 moved to the \000-\037
172 # range (ASCII control characters), and a few extra characters thrown
173 # in. It is basically a modified Windows 1252 codepage, minus, for
174 # now, the euro sign (\200 is reserved for euro.)
178 undef, undef, undef, undef, undef, undef, undef, undef, undef, undef,
179 undef, undef, undef, undef, undef, undef, 'dotlessi', 'grave',
180 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
181 'dieresis', undef, 'ring', 'cedilla', undef, 'hungarumlaut',
182 'ogonek', 'caron', 'space', 'exclam', 'quotedbl', 'numbersign',
183 'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft',
184 'parenright', 'asterisk', 'plus', 'comma', 'minus', 'period',
185 'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six',
186 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal',
187 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
188 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
189 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright',
190 'asciicircum', 'underscore', 'quoteleft', 'a', 'b', 'c', 'd', 'e',
191 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
192 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
193 'asciitilde', undef, undef, undef, 'quotesinglbase', 'florin',
194 'quotedblbase', 'ellipsis', 'dagger', 'dbldagger', 'circumflex',
195 'perthousand', 'Scaron', 'guilsinglleft', 'OE', undef, 'Zcaron',
196 undef, undef, 'grave', 'quotesingle', 'quotedblleft',
197 'quotedblright', 'bullet', 'endash', 'emdash', 'tilde', 'trademark',
198 'scaron', 'guilsignlright', 'oe', undef, 'zcaron', 'Ydieresis',
199 'space', 'exclamdown', 'cent', 'sterling', 'currency', 'yen',
200 'brokenbar', 'section', 'dieresis', 'copyright', 'ordfeminine',
201 'guillemotleft', 'logicalnot', 'hyphen', 'registered', 'macron',
202 'degree', 'plusminus', 'twosuperior', 'threesuperior', 'acute', 'mu',
203 'paragraph', 'periodcentered', 'cedilla', 'onesuperior',
204 'ordmasculine', 'guillemotright', 'onequarter', 'onehalf',
205 'threequarters', 'questiondown', 'Agrave', 'Aacute', 'Acircumflex',
206 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla', 'Egrave', 'Eacute',
207 'Ecircumflex', 'Edieresis', 'Igrave', 'Iacute', 'Icircumflex',
208 'Idieresis', 'Eth', 'Ntilde', 'Ograve', 'Oacute', 'Ocircumflex',
209 'Otilde', 'Odieresis', 'multiply', 'Oslash', 'Ugrave', 'Uacute',
210 'Ucircumflex', 'Udieresis', 'Yacute', 'Thorn', 'germandbls',
211 'agrave', 'aacute', 'acircumflex', 'atilde', 'adieresis', 'aring',
212 'ae', 'ccedilla', 'egrave', 'eacute', 'ecircumflex', 'edieresis',
213 'igrave', 'iacute', 'icircumflex', 'idieresis', 'eth', 'ntilde',
214 'ograve', 'oacute', 'ocircumflex', 'otilde', 'odieresis', 'divide',
215 'oslash', 'ugrave', 'uacute', 'ucircumflex', 'udieresis', 'yacute',
219 # Name-to-byte lookup hash
221 for ( $i = 0 ; $i < 256 ; $i++ ) {
222 $charcode{$NASMEncoding[$i]} = chr($i);
226 # First, format the stuff coming from the front end into
227 # a cleaner representation
229 if ( defined($input) ) {
230 open(PARAS
, '<', $input) or
231 die "$0: cannot open $input: $!\n";
234 open(PARAS
, '<-') or die "$0: $!\n";
236 while ( defined($line = <PARAS
>) ) {
240 if ( $line =~ /^meta :(.*)$/ ) {
242 $metadata{$metakey} = $data;
243 } elsif ( $line =~ /^indx :(.*)$/ ) {
245 push(@ixentries, $ixentry);
246 $ixterms{$ixentry} = [split(/\037/, $data)];
247 # Look for commas. This is easier done on the string
248 # representation, so do it now.
249 if ( $data =~ /^(.*)\,\037sp\037/ ) {
251 $ixprefix =~ s/\037n $//; # Discard possible font change at end
252 $ixhasprefix{$ixentry} = $ixprefix;
253 if ( !$ixprefixes{$ixprefix} ) {
254 $ixcommafirst{$ixentry}++;
256 $ixprefixes{$ixprefix}++;
258 # A complete term can also be used as a prefix
259 $ixprefixes{$data}++;
262 push(@ptypes, $line);
263 push(@paras, [split(/\037/, $data)]);
269 # Convert an integer to a chosen base
275 my($z) = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
276 return '0' if ($i == 0);
277 if ( $i < 0 ) { $n = '-'; $i = -$i; }
279 $s = substr($z,$i%$b,1) . $s;
286 # Convert a string to a rendering array
293 $s =~ s/\B\-\-\B/$charcode{'emdash'}/g;
294 $s =~ s/\B\-\B/ $charcode{'endash'} /g;
296 while ( $s =~ /^(\s+|\S+)(.*)$/ ) {
305 # Take a crossreference name and generate the PostScript name for it.
307 # This hack produces a somewhat smaller PDF...
312 # my $q = $ps_xref_list{$s};
313 # return $q if ( defined($ps_xref_list{$s}) );
314 # $q = 'X'.int2base($ps_xref_next++, 52);
315 # $ps_xref_list{$s} = $q;
319 # Somewhat bigger PDF, but one which obeys # URLs
325 # Flow lines according to a particular font set and width
327 # A "font set" is represented as an array containing
328 # arrays of pairs: [<size>, <metricref>]
330 # Each line is represented as:
331 # [ [type,first|last,aux,fontset,page,ypos,optional col],
332 # [rendering array] ]
334 # A space character may be "squeezed" by up to this much
335 # (as a fraction of the normal width of a space.)
337 $ps_space_squeeze = 0.00; # Min space width 100%
338 sub ps_flow_lines
($$$@
) {
339 my($wid, $fontset, $type, @data) = @_;
340 my($fonts) = $$fontset{fonts
};
342 my($w) = 0; # Width of current line
343 my($sw) = 0; # Width of current line due to spaces
344 my(@l) = (); # Current line
345 my(@ls) = (); # Accumulated output lines
346 my(@xd) = (); # Metadata that goes with subsequent text
347 my $hasmarker = 0; # Line has -6 marker
348 my $pastmarker = 0; # -6 marker found
350 # If there is a -6 marker anywhere in the paragraph,
351 # *each line* output needs to have a -6 marker
352 foreach $e ( @data ) {
353 $hasmarker = 1 if ( $$e[0] == -6 );
357 foreach $e ( @data ) {
359 # Type is metadata. Zero width.
360 if ( $$e[0] == -6 ) {
363 if ( $$e[0] == -1 || $$e[0] == -6 ) {
364 # -1 (end anchor) or -6 (marker) goes with the preceeding
365 # text, otherwise with the subsequent text
371 my $ew = ps_width
($$e[1], $fontset->{fonts
}->[$$e[0]][1],
373 ($fontset->{fonts
}->[$$e[0]][0]);
375 $sp =~ tr/[^ ]//d; # Delete nonspaces
376 my $esw = ps_width
($sp, $fontset->{fonts
}->[$$e[0]][1],
378 ($fontset->{fonts
}->[$$e[0]][0]);
380 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
382 # Search backwards for previous space chunk
383 my $lx = scalar(@l)-1;
386 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) {
388 $pastmarker = 0 if ( $l[$lx]->[0] == -6 );
392 if ( $l[$lx]->[1] eq ' ' ) {
394 @rm = splice(@l, $lx);
395 last; # Found place to break
402 # Now @l contains the stuff to remain on the old line
403 # If we broke the line inside a link, then split the link
406 foreach my $lc ( @l ) {
407 if ( $$lc[0] == -2 || $$lc[0] == -3 || $lc[0] == -7 ) {
409 } elsif ( $$lc[0] == -1 ) {
414 if ( defined($lkref) ) {
415 push(@l, [-1,undef]); # Terminate old reference
416 unshift(@rm, $lkref); # Duplicate reference on new line
421 unshift(@rm,[-6,undef]); # New line starts with marker
423 push(@l,[-6,undef]); # Old line ends with marker
427 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
431 # Compute the width of the remainder array
433 if ( $$le[0] >= 0 ) {
434 my $xew = ps_width
($$le[1],
435 $fontset->{fonts
}->[$$le[0]][1],
437 ($fontset->{fonts
}->[$$le[0]][0]);
439 $xsp =~ tr/[^ ]//d; # Delete nonspaces
440 my $xsw = ps_width
($xsp,
441 $fontset->{fonts
}->[$$le[0]][1],
443 ($fontset->{fonts
}->[$$le[0]][0]);
444 $w += $xew; $sw += $xsw;
448 push(@l, @xd); # Accumulated metadata
450 if ( $$e[1] ne '' ) {
452 $w += $ew; $sw += $esw;
458 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
461 # Mark the first line as first and the last line as last
463 $ls[0]->[0]->[1] |= 1; # First in para
464 $ls[-1]->[0]->[1] |= 2; # Last in para
470 # Once we have broken things into lines, having multiple chunks
471 # with the same font index is no longer meaningful. Merge
472 # adjacent chunks to keep down the size of the whole file.
474 sub ps_merge_chunks
(@
) {
481 $eco = -1; # Index of the last entry in @co
483 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
484 $co[$eco]->[1] .= $$c[1];
486 push(@co, $c); $eco++;
494 # Convert paragraphs to rendering arrays. Each
495 # element in the array contains (font, string),
496 # where font can be one of:
500 # -4 index item anchor
502 # -6 left/right marker (used in the index)
503 # -7 page link (used in the index)
506 # 2 code (fixed spacing)
509 sub mkparaarray
($@
) {
510 my($ptype, @chunks) = @_;
516 if ( $ptype =~ /^code/ ) {
517 foreach $chunk ( @chunks ) {
518 push(@para, [2, $chunk]);
521 foreach $chunk ( @chunks ) {
522 my $type = substr($chunk,0,2);
523 my $text = substr($chunk,2);
525 if ( $type eq 'sp' ) {
526 push(@para, [$in_e?
1:0, ' ']);
527 } elsif ( $type eq 'da' ) {
528 push(@para, [$in_e?
1:0, $charcode{'endash'}]);
529 } elsif ( $type eq 'n ' ) {
530 push(@para, [0, $text]);
532 } elsif ( $type =~ '^e' ) {
533 push(@para, [1, $text]);
534 $in_e = ($type eq 'es' || $type eq 'e ');
535 } elsif ( $type eq 'c ' ) {
536 push(@para, [2, $text]);
538 } elsif ( $type eq 'x ' ) {
539 push(@para, [-2, ps_xref
($text)]);
540 } elsif ( $type eq 'xe' ) {
541 push(@para, [-1, undef]);
542 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
543 $text =~ /\<(.*)\>(.*)$/;
544 my $link = $1; $text = $2;
545 push(@para, [-3, $link]);
546 push(@para, [($type eq 'wc') ?
2:0, $text]);
547 push(@para, [-1, undef]);
549 } elsif ( $type eq 'i ' ) {
550 push(@para, [-4, $text]);
552 die "Unexpected paragraph chunk: $chunk";
559 $npara = scalar(@paras);
560 for ( $i = 0 ; $i < $npara ; $i++ ) {
561 $paras[$i] = [mkparaarray
($ptypes[$i], @
{$paras[$i]})];
565 # This converts a rendering array to a simple string
567 sub ps_arraytostr
(@
) {
571 $s .= $$c[1] if ( $$c[0] >= 0 );
577 # This generates a duplicate of a paragraph
592 # This generates a duplicate of a paragraph, stripping anchor
595 sub ps_dup_para_noanchor
(@
) {
602 push(@o, [@cc]) unless ( $cc[0] == -4 || $cc[0] == -5 );
608 # Scan for header paragraphs and fix up their contents;
609 # also generate table of contents and PDF bookmarks.
611 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
612 @tocptypes = ('chap');
613 @bookmarks = (['title', 0, 'Title'], ['contents', 0, 'Contents']);
615 for ( $i = 0 ; $i < $npara ; $i++ ) {
616 my $xtype = $ptypes[$i];
617 my $ptype = substr($xtype,0,4);
621 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
622 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
627 my $xref = ps_xref
($sech);
628 my $chap = ($ptype eq 'chap')?
'Chapter':'Appendix';
630 $book = [$xref, 0, ps_arraytostr
(@
{$paras[$i]})];
631 push(@bookmarks, $book);
632 $bookref{$secn} = $book;
634 push(@tocparas, [ps_dup_para_noanchor
(@
{$paras[$i]})]);
635 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
637 unshift(@
{$paras[$i]},
638 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
639 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
640 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
645 my $xref = ps_xref
($sech);
647 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
649 $book = [$xref, 0, ps_arraytostr
(@
{$paras[$i]})];
650 push(@bookmarks, $book);
651 $bookref{$secn} = $book;
652 $bookref{$pref}->[1]--; # Adjust count for parent node
654 push(@tocparas, [ps_dup_para_noanchor
(@
{$paras[$i]})]);
656 (($ptype eq 'subh') ?
'toc2':'toc1').' :'.$sech.':'.$secn);
658 unshift(@
{$paras[$i]}, [-5, $xref]);
663 # Add TOC to beginning of paragraph list
665 unshift(@paras, @tocparas); undef @tocparas;
666 unshift(@ptypes, @tocptypes); undef @tocptypes;
669 # Add copyright notice to the beginning
672 ([[0, $charcode{'copyright'}],
673 [0, ' '], [0, $metadata{'year'}],
674 [0, ' '], string2array
($metadata{'author'}),
675 [0, ' '], string2array
($metadata{'copyright_tail'})],
676 [string2array
($metadata{'license'})],
677 [string2array
($metadata{'auxinfo'})]);
679 unshift(@paras, @copyright_page);
680 unshift(@ptypes, ('norm') x
scalar(@copyright_page));
682 $npara = scalar(@paras);
685 # No lines generated, yet.
690 # Line Auxilliary Information Types
692 $AuxStr = 1; # String
693 $AuxPage = 2; # Page number (from xref)
694 $AuxPageStr = 3; # Page number as a PostScript string
695 $AuxXRef = 4; # Cross reference as a name
696 $AuxNum = 5; # Number
699 # Break or convert paragraphs into lines, and push them
700 # onto the @pslines array.
702 sub ps_break_lines
($$) {
703 my ($paras,$ptypes) = @_;
705 my $linewidth = $psconf{pagewidth
}-$psconf{lmarg
}-$psconf{rmarg
};
706 my $bullwidth = $linewidth-$psconf{bulladj
};
707 my $indxwidth = ($linewidth-$psconf{idxgutter
})/$psconf{idxcolumns
}
710 my $npara = scalar(@
{$paras});
713 for ( $i = 0 ; $i < $npara ; $i++ ) {
714 my $xtype = $ptypes->[$i];
715 my $ptype = substr($xtype,0,4);
716 my @data = @
{$paras->[$i]};
718 if ( $ptype eq 'code' ) {
720 # Code paragraph; each chunk is a line
721 foreach $p ( @data ) {
722 push(@ls, [[$ptype,0,undef,\
%BodyFont,0,0],[$p]]);
724 $ls[0]->[0]->[1] |= 1; # First in para
725 $ls[-1]->[0]->[1] |= 2; # Last in para
726 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
727 # Chapters are flowed normally, but in an unusual font
728 @ls = ps_flow_lines
($linewidth, \
%ChapFont, $ptype, @data);
729 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
730 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
735 my $font = ($ptype eq 'head') ? \
%HeadFont : \
%SubhFont;
736 @ls = ps_flow_lines
($linewidth, $font, $ptype, @data);
737 # We need the heading number as auxillary data
738 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
739 } elsif ( $ptype eq 'norm' ) {
740 @ls = ps_flow_lines
($linewidth, \
%BodyFont, $ptype, @data);
741 } elsif ( $ptype =~ /^(bull|indt)$/ ) {
742 @ls = ps_flow_lines
($bullwidth, \
%BodyFont, $ptype, @data);
743 } elsif ( $ptypq eq 'bquo' ) {
744 @ls = ps_flow_lines
($bullwidth, \
%BquoFont, $ptype, @data);
745 } elsif ( $ptype =~ /^toc/ ) {
746 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
750 my $refname = $2.' ';
751 my $ntoc = substr($ptype,3,1)+0;
752 my $refwidth = ps_width
($refname, $BodyFont{fonts
}->[0][1],
754 ($BodyFont{fonts
}->[0][0]);
756 @ls = ps_flow_lines
($linewidth-$ntoc*$psconf{tocind
}-
757 $psconf{tocpnz
}-$refwidth,
758 \
%BodyFont, $ptype, @data);
760 # Auxilliary data: for the first line, the cross reference symbol
761 # and the reference name; for all lines but the first, the
762 # reference width; and for the last line, the page number
764 my $nl = scalar(@ls);
765 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
766 for ( $j = 1 ; $j < $nl ; $j++ ) {
767 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
769 push(@
{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
770 } elsif ( $ptype =~ /^idx/ ) {
771 my $lvl = substr($ptype,3,1)+0;
773 @ls = ps_flow_lines
($indxwidth-$lvl*$psconf{idxindent
},
774 \
%BodyFont, $ptype, @data);
776 die "Unknown para type: $ptype";
778 # Merge adjacent identical chunks
780 @
{$$l[1]} = ps_merge_chunks
(@
{$$l[1]});
786 # Break the main body text into lines.
787 ps_break_lines
(\
@paras, \
@ptypes);
790 # Break lines in to pages
793 # Where to start on page 2, the copyright page
794 $curpage = 2; # Start on page 2
795 $curypos = $psconf{pageheight
}-$psconf{topmarg
}-$psconf{botmarg
}-
796 $psconf{startcopyright
};
797 undef $columnstart; # Not outputting columnar text
798 undef $curcolumn; # Current column
799 $nlines = scalar(@pslines);
802 # This formats lines inside the global @pslines array into pages,
803 # updating the page and y-coordinate entries. Start at the
804 # $startline position in @pslines and go to but not including
805 # $endline. The global variables $curpage, $curypos, $columnstart
806 # and $curcolumn are updated appropriately.
808 sub ps_break_pages
($$) {
809 my($startline, $endline) = @_;
811 # Paragraph types which should never be broken
812 my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
813 # Paragraph types which are heading (meaning they should not be broken
815 my $nobreakafter = "^(chap|appn|head|subh)\$";
816 # Paragraph types which should never be broken *before*
817 my $nobreakbefore = "^idx[1-9]\$";
818 # Paragraph types which are set in columnar format
819 my $columnregexp = "^idx.\$";
821 my $upageheight = $psconf{pageheight
}-$psconf{topmarg
}-$psconf{botmarg
};
825 for ( $i = $startline ; $i < $endline ; $i++ ) {
826 my $linfo = $pslines[$i]->[0];
827 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
828 && ($$linfo[1] & 1) ) {
829 # First line of a new chapter heading. Start a new page.
831 $curpage++ if ( $curypos > 0 || defined($columnstart) );
832 # Always start on an odd page
834 $curypos = $chapstart;
835 } elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
841 if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
842 $columnstart = $curypos;
846 # Adjust position by the appropriate leading
847 $curypos += $$linfo[3]->{leading
};
849 # Record the page and y-position
850 $$linfo[4] = $curpage;
851 $$linfo[5] = $curypos;
852 $$linfo[6] = $curcolumn if ( defined($columnstart) );
854 if ( $curypos > $upageheight ) {
855 # We need to break the page before this line.
856 my $broken = 0; # No place found yet
857 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
858 my $linfo = $pslines[$i]->[0];
859 my $pinfo = $pslines[$i-1]->[0];
861 if ( $$linfo[1] == 2 ) {
862 # This would be an orphan, don't break.
863 } elsif ( $$linfo[1] & 1 ) {
864 # Sole line or start of paragraph. Break unless
865 # the previous line was part of a heading.
866 $broken = 1 if ( $$pinfo[0] !~ /$nobreakafter/o &&
867 $$linfo[0] !~ /$nobreakbefore/o );
869 # Middle of paragraph. Break unless we're in a
870 # no-break paragraph, or the previous line would
871 # end up being a widow.
872 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
877 die "Nowhere to break page $curpage\n" if ( !$broken );
878 # Now $i should point to line immediately before the break, i.e.
879 # the next paragraph should be the first on the new page
880 if ( defined($columnstart) &&
881 ++$curcolumn < $psconf{idxcolumns
} ) {
882 # We're actually breaking text into columns, not pages
883 $curypos = $columnstart;
892 # Add end of paragraph skip
893 if ( $$linfo[1] & 2 ) {
894 $curypos += $skiparray{$$linfo[0]};
899 ps_break_pages
(0,$nlines); # Break the main text body into pages
902 # Find the page number of all the indices
904 %ps_xref_page = (); # Crossref anchor pages
905 %ps_index_pages = (); # Index item pages
906 $nlines = scalar(@pslines);
907 for ( $i = 0 ; $i < $nlines ; $i++ ) {
908 my $linfo = $pslines[$i]->[0];
909 foreach my $c ( @
{$pslines[$i]->[1]} ) {
910 if ( $$c[0] == -4 ) {
911 if ( !defined($ps_index_pages{$$c[1]}) ) {
912 $ps_index_pages{$$c[1]} = [];
913 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
914 # Pages are emitted in order; if this is a duplicated
915 # entry it will be the last one
918 push(@
{$ps_index_pages{$$c[1]}}, $$linfo[4]);
919 } elsif ( $$c[0] == -5 ) {
920 $ps_xref_page{$$c[1]} = $$linfo[4];
926 # Emit index paragraphs
928 $startofindex = scalar(@pslines);
929 @ixparas = ([[-5,'index'],[0,'Index']]);
930 @ixptypes = ('chap');
932 foreach $k ( @ixentries ) {
934 my $ixptype = 'idx0';
935 my $prefix = $ixhasprefix{$k};
936 my @ixpara = mkparaarray
($ixptype,@
{$ixterms{$k}});
937 my $commapos = undef;
939 if ( defined($prefix) && $ixprefixes{$prefix} > 1 ) {
940 # This entry has a "hanging comma"
941 for ( $i = 0 ; $i < scalar(@ixpara)-1 ; $i++ ) {
942 if ( substr($ixpara[$i]->[1],-1,1) eq ',' &&
943 $ixpara[$i+1]->[1] eq ' ' ) {
949 if ( defined($commapos) ) {
950 if ( $ixcommafirst{$k} ) {
951 # This is the first entry; generate the
952 # "hanging comma" entry
953 my @precomma = splice(@ixpara,0,$commapos);
954 if ( $ixpara[0]->[1] eq ',' ) {
955 shift(@ixpara); # Discard lone comma
957 # Discard attached comma
958 $ixpara[0]->[1] =~ s/\,$//;
959 push(@precomma,shift(@ixpara));
961 push(@precomma, [-6,undef]);
962 push(@ixparas, [@precomma]);
963 push(@ixptypes, $ixptype);
964 shift(@ixpara); # Remove space
966 splice(@ixpara,0,$commapos+2);
971 push(@ixpara, [-6,undef]); # Left/right marker
972 $i = 1; $n = scalar(@
{$ps_index_pages{$k}});
973 foreach $p ( @
{$ps_index_pages{$k}} ) {
975 push(@ixpara,[-7,$p],[0,"$p"],[-1,undef]);
977 push(@ixpara,[-7,$p],[0,"$p,"],[-1,undef],[0,' ']);
981 push(@ixparas, [@ixpara]);
982 push(@ixptypes, $ixptype);
986 # Flow index paragraphs into lines
988 ps_break_lines
(\
@ixparas, \
@ixptypes);
991 # Format index into pages
993 $nlines = scalar(@pslines);
994 ps_break_pages
($startofindex, $nlines);
997 # Push index onto bookmark list
999 push(@bookmarks, ['index', 0, 'Index']);
1001 @all_fonts_lst = sort(keys(%ps_all_fonts));
1002 $all_fonts_str = join(' ', @all_fonts_lst);
1003 @need_fonts_lst = ();
1004 foreach my $f (@all_fonts_lst) {
1005 push(@need_fonts_lst, $f); # unless (defined($ps_all_fonts{$f}->{file}));
1007 $need_fonts_str = join(' ', @need_fonts_lst);
1009 # Emit the PostScript DSC header
1010 print "%!PS-Adobe-3.0\n";
1011 print "%%Pages: $curpage\n";
1012 print "%%BoundingBox: 0 0 ", $psconf{pagewidth
}, ' ', $psconf{pageheight
}, "\n";
1013 print "%%Creator: (NASM psflow.pl)\n";
1014 print "%%DocumentData: Clean7Bit\n";
1015 print "%%DocumentFonts: $all_fonts_str\n";
1016 print "%%DocumentNeededFonts: $need_fonts_str\n";
1017 print "%%Orientation: Portrait\n";
1018 print "%%PageOrder: Ascend\n";
1019 print "%%EndComments\n";
1020 print "%%BeginProlog\n";
1022 # Emit the configurables as PostScript tokens
1023 foreach $c ( keys(%psconf) ) {
1024 print "/$c ", $psconf{$c}, " def\n";
1026 foreach $c ( keys(%psbool) ) {
1027 print "/$c ", ($psbool{$c}?
'true':'false'), " def\n";
1030 # Embed font data, if applicable
1031 #foreach my $f (@all_fonts_lst) {
1032 # my $fontfile = $all_ps_fonts{$f}->{file};
1033 # if (defined($fontfile)) {
1034 # if (open(my $fh, '<', $fontfile)) {
1035 # print vector <$fh>;
1041 # Emit custom encoding vector
1042 $zstr = '/NASMEncoding [ ';
1043 foreach $c ( @NASMEncoding ) {
1044 my $z = '/'.(defined($c)?
$c:'.notdef ').' ';
1045 if ( length($zstr)+length($z) > 72 ) {
1051 print $zstr, "] def\n";
1053 # Font recoding routine
1054 # newname fontname --
1055 print "/nasmenc {\n";
1056 print " findfont dup length dict begin\n";
1057 print " { 1 index /FID ne {def}{pop pop} ifelse } forall\n";
1058 print " /Encoding NASMEncoding def\n";
1059 print " currentdict\n";
1061 print " definefont pop\n";
1064 # Emit fontset definitions
1065 foreach $font ( sort(keys(%ps_all_fonts)) ) {
1066 print '/',$font,'-NASM /',$font," nasmenc\n";
1069 foreach $fset ( @AllFonts ) {
1072 foreach $font ( @
{$fset->{fonts
}} ) {
1073 print '/', $fset->{name
}, $i, ' ',
1074 '/', $font->[1]->{name
}, '-NASM findfont ',
1075 $font->[0], " scalefont def\n";
1076 push(@zfonts, $fset->{name
}.$i);
1079 print '/', $fset->{name
}, ' [', join(' ',@zfonts), "] def\n";
1082 # This is used by the bullet-paragraph PostScript methods
1083 print "/bullet [",ps_string
($charcode{'bullet'}),"] def\n";
1085 # Emit the canned PostScript prologue
1086 open(PSHEAD
, '<', $headps)
1087 or die "$0: cannot open: $headps: $!\n";
1088 while ( defined($line = <PSHEAD
>) ) {
1092 print "%%EndProlog\n";
1094 # Generate a PostScript string
1099 my ($l) = length($s);
1100 for ( $i = 0 ; $i < $l ; $i++ ) {
1101 $c = substr($s,$i,1);
1102 if ( ord($c) < 32 || ord($c) > 126 ) {
1103 $o .= sprintf("\\%03o", ord($c));
1104 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
1113 # Generate PDF bookmarks
1114 print "%%BeginSetup\n";
1115 foreach $b ( @bookmarks ) {
1116 print '[/Title ', ps_string
($b->[2]), "\n";
1117 print '/Count ', $b->[1], ' ' if ( $b->[1] );
1118 print '/Dest /',$b->[0]," /OUT pdfmark\n";
1121 # Ask the PostScript interpreter for the proper size media
1122 print "setpagesize\n";
1123 print "%%EndSetup\n";
1125 # Start a PostScript page
1126 sub ps_start_page
() {
1128 print "%%Page: $ps_page $ps_page\n";
1129 print "%%BeginPageSetup\n";
1131 print "%%EndPageSetup\n";
1132 print '/', $ps_page, " pa\n";
1135 # End a PostScript page
1136 sub ps_end_page
($) {
1139 print "($ps_page)", (($ps_page & 1) ?
'pageodd' : 'pageeven'), "\n";
1141 print "restore showpage\n";
1148 $title = $metadata{'title'} || '';
1149 $title =~ s/ \- / $charcode{'endash'} /;
1151 $subtitle = $metadata{'subtitle'} || '';
1152 $subtitle =~ s/ \- / $charcode{'endash'} /;
1155 print "/ti ", ps_string
($title), " def\n";
1156 print "/sti ", ps_string
($subtitle), " def\n";
1157 print "lmarg pageheight 2 mul 3 div moveto\n";
1158 print "tfont0 setfont\n";
1159 print "/title linkdest ti show\n";
1160 print "lmarg pageheight 2 mul 3 div 10 sub moveto\n";
1161 print "0 setlinecap 3 setlinewidth\n";
1162 print "pagewidth lmarg sub rmarg sub 0 rlineto currentpoint stroke moveto\n";
1163 print "hfont1 setfont sti stringwidth pop neg ",
1164 -$HeadFont{leading
}, " rmoveto\n";
1167 # Print logo, if there is one
1168 # FIX: To be 100% correct, this should look for DocumentNeeded*
1169 # and DocumentFonts in the header of the EPSF and add those to the
1171 if ( defined($metadata{epslogo
}) &&
1172 open(EPS
, '<', File
::Spec
->catfile($epsdir, $metadata{epslogo
})) ) {
1174 my ($bbllx,$bblly,$bburx,$bbury) = (undef,undef,undef,undef);
1177 my $maxwidth = $psconf{pagewidth
}-$psconf{lmarg
}-$psconf{rmarg
};
1178 my $maxheight = $psconf{pageheight
}/3-40;
1182 while ( defined($line = <EPS
>) ) {
1183 last if ( $line =~ /^%%EOF/ );
1184 if ( !defined($bbllx) &&
1185 $line =~ /^\%\%BoundingBox\:\s*([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/i ) {
1186 $bbllx = $1+0; $bblly = $2+0;
1187 $bburx = $3+0; $bbury = $4+0;
1193 if ( defined($bbllx) ) {
1194 $width = $bburx-$bbllx;
1195 $height = $bbury-$bblly;
1197 if ( $width > $maxwidth ) {
1198 $scale = $maxwidth/$width;
1200 if ( $height*$scale > $maxheight ) {
1201 $scale = $maxheight/$height;
1204 $x = ($psconf{pagewidth
}-$width*$scale)/2;
1205 $y = ($psconf{pageheight
}-$height*$scale)/2;
1207 if ( defined($metadata{logoxadj
}) ) {
1208 $x += $metadata{logoxadj
};
1210 if ( defined($metadata{logoyadj
}) ) {
1211 $y += $metadata{logoyadj
};
1214 print "BeginEPSF\n";
1215 print $x, ' ', $y, " translate\n";
1216 print $scale, " dup scale\n" unless ( $scale == 1 );
1217 print -$bbllx, ' ', -$bblly, " translate\n";
1218 print "$bbllx $bblly moveto\n";
1219 print "$bburx $bblly lineto\n";
1220 print "$bburx $bbury lineto\n";
1221 print "$bbllx $bbury lineto\n";
1222 print "$bbllx $bblly lineto clip newpath\n";
1223 print "%%BeginDocument: ",ps_string
($metadata{epslogo
}),"\n";
1225 print "%%EndDocument\n";
1231 # Emit the rest of the document (page 2 and on)
1234 foreach $line ( @pslines ) {
1235 my $linfo = $line->[0];
1237 while ( $$linfo[4] > $curpage ) {
1238 ps_end_page
($curpage > 2);
1245 foreach my $c ( @
{$line->[1]} ) {
1246 if ( $$c[0] >= 0 ) {
1247 if ( $curfont != $$c[0] ) {
1248 print ($curfont = $$c[0]);
1250 print ps_string
($$c[1]);
1251 } elsif ( $$c[0] == -1 ) {
1252 print '{el}'; # End link
1253 } elsif ( $$c[0] == -2 ) {
1254 print '{/',$$c[1],' xl}'; # xref link
1255 } elsif ( $$c[0] == -3 ) {
1256 print '{',ps_string
($$c[1]),'wl}'; # web link
1257 } elsif ( $$c[0] == -4 ) {
1258 # Index anchor -- ignore
1259 } elsif ( $$c[0] == -5 ) {
1260 print '{/',$$c[1],' xa}'; #xref anchor
1261 } elsif ( $$c[0] == -6 ) {
1262 print ']['; # Start a new array
1264 } elsif ( $$c[0] == -7 ) {
1265 print '{/',$$c[1],' pl}'; # page link
1267 die "Unknown annotation";
1271 if ( defined($$linfo[2]) ) {
1272 foreach my $x ( @
{$$linfo[2]} ) {
1273 if ( $$x[0] == $AuxStr ) {
1274 print ps_string
($$x[1]);
1275 } elsif ( $$x[0] == $AuxPage ) {
1276 print $ps_xref_page{$$x[1]},' ';
1277 } elsif ( $$x[0] == $AuxPageStr ) {
1278 print ps_string
($ps_xref_page{$$x[1]});
1279 } elsif ( $$x[0] == $AuxXRef ) {
1280 print '/',ps_xref
($$x[1]),' ';
1281 } elsif ( $$x[0] == $AuxNum ) {
1284 die "Unknown auxilliary data type";
1288 print ($psconf{pageheight
}-$psconf{topmarg
}-$$linfo[5]);
1289 print ' ', $$linfo[6] if ( defined($$linfo[6]) );
1290 print ' ', $$linfo[0].$$linfo[1], "\n";