3 # Format the documentation as PostScript
6 require 'psfonts.ph'; # The fonts we want to use
7 require 'pswidth.ph'; # PostScript string width
10 # PostScript configurables; these values are also available to the
11 # PostScript code itself
14 pagewidth => 595, # Page width in PostScript points
15 pageheight => 792, # Page height in PostScript points
16 lmarg => 100, # Left margin in PostScript points
17 rmarg => 50, # Right margin in PostScript points
18 topmarg => 100, # Top margin in PostScript points
19 botmarg => 100, # Bottom margin in PostScript points
20 plmarg => 50, # Page number position relative to left margin
21 prmarg => 0, # Page number position relative to right margin
22 pymarg => 50, # Page number position relative to bot margin
23 bulladj => 12, # How much to indent a bullet paragraph
24 tocind => 12, # TOC indentation per level
25 tocpnz => 24, # Width of TOC page number only zone
26 tocdots => 8, # Spacing between TOC dots
27 idxspace => 24, # Minimum space between index title and pg#
28 idxindent => 32, # How much to indent a subindex entry
29 idxgutter => 24, # Space between index columns
33 # $psconf{pagewidth} = 612; $psconf{pageheight} = 792;
35 # $psconf{pagewidth} = 595; $psconf{pageheight} = 842;
37 $paraskip = 6; # Space between paragraphs
38 $chapstart = 30; # Space before a chapter heading
39 $chapskip = 24; # Space after a chapter heading
40 $tocskip = 6; # Space between TOC entries
42 # Configure post-paragraph skips for each kind of paragraph
43 %skiparray = ('chap' => $chapskip, 'appn' => $chapstart,
44 'head' => $paraskip, 'subh' => $paraskip,
45 'norm' => $paraskip, 'bull' => $paraskip,
46 'code' => $paraskip, 'toc0' => $tocskip,
47 'toc1' => $tocskip, 'toc2' => $tocskip);
50 # First, format the stuff coming from the front end into
51 # a cleaner representation
53 open(PARAS, '< nasmdoc.dip');
54 while ( defined($line = <PARAS>) ) {
58 if ( $line =~ /^meta :/ ) {
60 $metadata{$metakey} = $data;
61 } elsif ( $line =~ /^indx :/ ) {
63 push(@ixentries, $ixentry);
64 $ixterms{$ixentry} = [split(/\037/, $data)];
65 # Look for commas. This is easier done on the string
66 # representation, so do it now.
67 if ( $line =~ /^(.*\,)\037sp\037/ ) {
69 $ixhasprefix{$ixentry} = $ixprefix;
70 if ( !$ixprefixes{$ixprefix} ) {
71 $ixcommafirst{$ixentry}++;
73 $ixprefixes{$ixprefix}++;
77 push(@paras, [split(/\037/, $data)]);
83 # Convert an integer to a chosen base
89 my($z) = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
90 return '0' if ($i == 0);
91 if ( $i < 0 ) { $n = '-'; $i = -$i; }
93 $s = substr($z,$i%$b,1) . $s;
100 # Take a crossreference name and generate the PostScript name for it.
104 return $s; # Identity transform should be OK for now
108 # Flow lines according to a particular font set and width
110 # A "font set" is represented as an array containing
111 # arrays of pairs: [<size>, <metricref>]
113 # Each line is represented as:
114 # [ [type,first|last,aux,fontset,page,ypos], [rendering array] ]
116 # A space character may be "squeezed" by up to this much
117 # (as a fraction of the normal width of a space.)
119 $ps_space_squeeze = 0.00; # Min space width 100%
120 sub ps_flow_lines($$$@) {
121 my($wid, $fontset, $type, @data) = @_;
122 my($fonts) = $$fontset{fonts};
124 my($w) = 0; # Width of current line
125 my($sw) = 0; # Width of current line due to spaces
126 my(@l) = (); # Current line
127 my(@ls) = (); # Accumulated output lines
128 my(@xd) = (); # Metadata that goes with subsequent text
131 foreach $e ( @data ) {
133 # Type is metadata. Zero width.
135 # -1 (end anchor) goes with the preceeding text, otherwise
136 # with the subsequent text
142 my $ew = ps_width($$e[1], $fontset->{fonts}->[$$e[0]][1]) *
143 ($fontset->{fonts}->[$$e[0]][0]/1000);
145 $sp =~ tr/[^ ]//d; # Delete nonspaces
146 my $esw = ps_width($sp, $fontset->{fonts}->[$$e[0]][1]) *
147 ($fontset->{fonts}->[$$e[0]][0]/1000);
149 if ( ($w+$ew) - $ps_space_squeeze*($sw+$esw) > $wid ) {
151 # Search backwards for previous space chunk
152 my $lx = scalar(@l)-1;
155 while ( $lx >= 0 && $l[$lx]->[0] < 0 ) { $lx-- }; # Skip metadata
157 if ( $l[$lx]->[1] eq ' ' ) {
159 @rm = splice(@l, $lx);
160 last; # Found place to break
167 # Now @l contains the stuff to remain on the old line
168 # If we broke the line inside a link of type -2 or -3,
169 # then split the link into two.
171 foreach my $lc ( @l ) {
172 if ( $$lc[0] == -2 || $$lc[0] == -3 ) {
174 } elsif ( $$lc[0] == -1 ) {
178 push(@l, [-1,undef]) if ( defined($lkref) );
179 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]);
181 unshift(@l, $lkref) if ( defined($lkref) );
183 # Compute the width of the remainder array
185 if ( $$le[0] >= 0 ) {
186 my $xew = ps_width($$le[1], $fontset->{fonts}->[$$le[0]][1]) *
187 ($fontset->{fonts}->[$$le[0]][0]/1000);
189 $xsp =~ tr/[^ ]//d; # Delete nonspaces
190 my $xsw = ps_width($xsp, $fontset->{fonts}->[$$le[0]][1]) *
191 ($fontset->{fonts}->[$$le[0]][0]/1000);
192 $w += $xew; $sw += $xsw;
196 push(@l, @xd); # Accumulated metadata
198 if ( $$e[1] ne '' ) {
200 $w += $ew; $sw += $esw;
206 push(@ls, [[$type,0,undef,$fontset,0,0],[@l]]); # Final line
209 # Mark the first line as first and the last line as last
211 $ls[0]->[0]->[1] |= 1; # First in para
212 $ls[-1]->[0]->[1] |= 2; # Last in para
218 # Once we have broken things into lines, having multiple chunks
219 # with the same font index is no longer meaningful. Merge
220 # adjacent chunks to keep down the size of the whole file.
222 sub ps_merge_chunks(@) {
229 $eco = -1; # Index of the last entry in @co
231 if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
232 $co[$eco]->[1] .= $$c[1];
234 push(@co, $c); $eco++;
242 # Convert paragraphs to rendering arrays. Each
243 # element in the array contains (font, string),
244 # where font can be one of:
248 # -4 index item anchor
252 # 2 code (fixed spacing)
255 sub mkparaarray($@) {
256 my($ptype, @chunks) = @_;
262 if ( $ptype =~ /^code/ ) {
263 foreach $chunk ( @{$paras[$i]} ) {
264 push(@para, [2, $chunk]);
267 foreach $chunk ( @{$paras[$i]} ) {
268 my $type = substr($chunk,0,2);
269 my $text = substr($chunk,2);
271 if ( $type eq 'sp' ) {
272 push(@para, [$in_e?1:0, ' ']);
273 } elsif ( $type eq 'da' ) {
274 # \261 is en dash in Adobe StandardEncoding
275 push(@para, [$in_e?1:0, "\261"]);
276 } elsif ( $type eq 'n ' ) {
277 push(@para, [0, $text]);
279 } elsif ( $type =~ '^e' ) {
280 push(@para, [1, $text]);
281 $in_e = ($type eq 'es' || $type eq 'e ');
282 } elsif ( $type eq 'c ' ) {
283 push(@para, [2, $text]);
285 } elsif ( $type eq 'x ' ) {
286 push(@para, [-2, ps_xref($text)]);
287 } elsif ( $type eq 'xe' ) {
288 push(@para, [-1, undef]);
289 } elsif ( $type eq 'wc' || $type eq 'w ' ) {
290 $text =~ /\<(.*)\>(.*)$/;
291 my $link = $1; $text = $2;
292 push(@para, [-3, $link]);
293 push(@para, [($type eq 'wc') ? 2:0, $text]);
294 push(@para, [-1, undef]);
296 } elsif ( $type eq 'i ' ) {
297 push(@para, [-4, $text]);
299 die "Unexpected paragraph chunk: $chunk";
306 $npara = scalar(@paras);
307 for ( $i = 0 ; $i < $npara ; $i++ ) {
308 $paras[$i] = [mkparaarray($ptypes[$i], @{$paras[$i]})];
312 # This converts a rendering array to a simple string
314 sub ps_arraytostr(@) {
318 $s .= $$c[1] if ( $$c[0] >= 0 );
324 # This generates a duplicate of a paragraph
339 # Scan for header paragraphs and fix up their contents;
340 # also generate table of contents and PDF bookmarks.
342 @tocparas = ([[-5, 'contents'], [0,'Contents']]);
343 @tocptypes = ('chap');
344 @bookmarks = (['title', 0, 'Title Page'], ['contents', 0, 'Contents']);
346 for ( $i = 0 ; $i < $npara ; $i++ ) {
347 my $xtype = $ptypes[$i];
348 my $ptype = substr($xtype,0,4);
352 if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
353 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
358 my $xref = ps_xref($sech);
359 my $chap = ($ptype eq 'chap')?'Chapter':'Appendix';
361 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
362 push(@bookmarks, $book);
363 $bookref{$secn} = $book;
365 push(@tocparas, [ps_dup_para(@{$paras[$i]})]);
366 push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
368 unshift(@{$paras[$i]},
369 [-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
370 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
371 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
376 my $xref = ps_xref($sech);
378 $pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
380 $book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
381 push(@bookmarks, $book);
382 $bookref{$secn} = $book;
383 $bookref{$pref}->[1]--; # Adjust count for parent node
385 push(@tocparas, [ps_dup_para(@{$paras[$i]})]);
387 (($ptype eq 'subh') ? 'toc2':'toc1').' :'.$sech.':'.$secn);
389 unshift(@{$paras[$i]}, [-5, $xref]);
394 # Add TOC to beginning of paragraph list
396 unshift(@paras, @tocparas);
397 unshift(@ptypes, @tocptypes);
398 $npara = scalar(@paras);
401 # Line Auxilliary Information Types
403 $AuxStr = 1; # String
404 $AuxPage = 2; # Page number (from xref)
405 $AuxPageStr = 3; # Page number as a PostScript string
406 $AuxXRef = 4; # Cross reference as a name
407 $AuxNum = 5; # Number
410 # Break or convert paragraphs into lines.
414 $linewidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
415 $bullwidth = $linewidth-$psconf{bulladj};
417 for ( $i = 0 ; $i < $npara ; $i++ ) {
418 my $xtype = $ptypes[$i];
419 my $ptype = substr($xtype,0,4);
420 my @data = @{$paras[$i]};
422 if ( $ptype eq 'code' ) {
424 # Code paragraph; each chunk is a line
425 foreach $p ( @data ) {
426 push(@ls, [[$ptype,0,undef,\%TextFont,0,0],[$p]]);
428 $ls[0]->[0]->[1] |= 1; # First in para
429 $ls[-1]->[0]->[1] |= 2; # Last in para
430 } elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
431 # Chapters are flowed normally, but in an unusual font
432 @ls = ps_flow_lines($linewidth, \%ChapFont, $ptype, @data);
433 } elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
434 unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
439 my $font = ($ptype eq 'head') ? \%HeadFont : \%SubhFont;
440 @ls = ps_flow_lines($linewidth, $font, $ptype, @data);
441 # We need the heading number as auxillary data
442 $ls[0]->[0]->[2] = [[$AuxStr,$secn]];
443 } elsif ( $ptype eq 'norm' ) {
444 @ls = ps_flow_lines($linewidth, \%TextFont, $ptype, @data);
445 } elsif ( $ptype eq 'bull' ) {
446 @ls = ps_flow_lines($bullwidth, \%TextFont, $ptype, @data);
447 } elsif ( $ptype =~ /^toc/ ) {
448 unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
452 my $refname = $2.' ';
453 my $ntoc = substr($ptype,3,1)+0;
454 my $refwidth = ps_width($refname, $TextFont{fonts}->[0][1]) *
455 ($TextFont{fonts}->[0][0]/1000);
457 @ls = ps_flow_lines($linewidth-$ntoc*$psconf{tocind}-
458 $psconf{tocpnz}-$refwidth,
459 \%TextFont, $ptype, @data);
461 # Auxilliary data: for the first line, the cross reference symbol
462 # and the reference name; for all lines but the first, the
463 # reference width; and for the last line, the page number
465 my $nl = scalar(@ls);
466 $ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
467 for ( $j = 1 ; $j < $nl ; $j++ ) {
468 $ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
470 push(@{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
472 die "Unknown para type: $ptype";
474 # Merge adjacent identical chunks
476 @{$$l[1]} = ps_merge_chunks(@{$$l[1]});
482 # Break lines in to pages
485 # Paragraph types which should never be broken
486 $nobreakregexp = "^(chap|appn|head|subh|toc.)\$";
487 # Paragraph types which are heading (meaning they should not be broken
489 $headingregexp = "^(chap|appn|head|subh)\$";
491 $curpage = 3; # First text page is page 3
492 $curypos = 0; # Space used on this page
493 $nlines = scalar(@pslines);
495 $upageheight = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg};
497 for ( $i = 0 ; $i < $nlines ; $i++ ) {
498 my $linfo = $pslines[$i]->[0];
499 if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn')
500 && ($$linfo[1] & 1) ) {
501 # First line of a new chapter heading. Start a new line.
502 $curpage++ if ( $curypos > 0 );
503 $curypos = $chapstart;
506 # Adjust position by the appropriate leading
507 $curypos += $$linfo[3]->{leading};
509 # Record the page and y-position
510 $$linfo[4] = $curpage;
511 $$linfo[5] = $curypos;
513 if ( $curypos > $upageheight ) {
514 # We need to break the page before this line.
515 my $broken = 0; # No place found yet
516 while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
517 my $linfo = $pslines[$i]->[0];
518 my $pinfo = $pslines[$i-1]->[0];
520 if ( $$linfo[1] == 2 ) {
521 # This would be an orphan, don't break.
522 } elsif ( $$linfo[1] & 1 ) {
523 # Sole line or start of paragraph. Break unless
524 # the previous line was part of a heading.
525 $broken = 1 if ( $$pinfo[0] !~ /$headingregexp/o );
527 # Middle of paragraph. Break unless we're in a
528 # no-break paragraph, or the previous line would
529 # end up being a widow.
530 $broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
535 die "Nowhere to break page $curpage\n" if ( !$broken );
536 # Now $i should point to line immediately before the break, i.e.
537 # the next paragraph should be the first on the new page
543 # Add end of paragraph skip
544 if ( $$linfo[1] & 2 ) {
545 $curypos += $skiparray{$$linfo[0]};
550 # Find the page number of all the indices
552 %ps_xref_page = (); # Crossref anchor pages
553 %ps_index_pages = (); # Index item pages
554 for ( $i = 0 ; $i < $nlines ; $i++ ) {
555 my $linfo = $pslines[$i]->[0];
556 foreach my $c ( @{$pslines[$i]->[1]} ) {
557 if ( $$c[0] == -4 ) {
558 if ( !defined($ps_index_pages{$$c[1]}) ) {
559 $ps_index_pages{$$c[1]} = [];
560 } elsif ( $ps_index_pages{$$c[1]}->[-1] eq $$linfo[4] ) {
561 # Pages are emitted in order; if this is a duplicated
562 # entry it will be the last one
565 push(@{$ps_index_pages{$$c[1]}}, $$linfo[4]);
566 } elsif ( $$c[0] == -5 ) {
567 $ps_xref_page{$$c[1]} = $$linfo[4];
572 # Get the list of fonts used
574 foreach $fset ( @AllFonts ) {
575 foreach $font ( @{$fset->{fonts}} ) {
576 $ps_all_fonts{$font->[1]->{name}}++;
580 # Emit the PostScript DSC header
581 print "%!PS-Adobe-3.0\n";
582 print "%%Pages: $curpage\n";
583 print "%%BoundingBox: 0 0 ", $psconf{pagewidth}, ' ', $psconf{pageheight}, "\n";
584 print "%%Creator: NASM psflow.pl\n";
585 print "%%DocumentData: Clean7Bit\n";
586 print "%%DocumentFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
587 print "%%DocumentNeededFonts: ", join(' ', keys(%ps_all_fonts)), "\n";
588 print "%%Orientation: Portrait\n";
589 print "%%PageOrder: Ascend\n";
590 print "%%EndComments\n";
591 print "%%BeginProlog\n";
593 # Emit the configurables as PostScript tokens
594 for $c ( keys(%psconf) ) {
595 print "/$c ", $psconf{$c}, " def\n";
598 # Emit fontset definitions
599 foreach $fset ( @AllFonts ) {
602 foreach $font ( @{$fset->{fonts}} ) {
603 print '/', $fset->{name}, $i, ' ',
604 '/', $font->[1]->{name}, ' findfont ',
605 $font->[0], " scalefont def\n";
606 push(@zfonts, $fset->{name}.$i);
609 print '/', $fset->{name}, ' [', join(' ',@zfonts), "] def\n";
612 # Emit the result as PostScript. This is *NOT* correct code yet!
613 open(PSHEAD, "< head.ps");
614 while ( defined($line = <PSHEAD>) ) {
618 print "%%EndProlog\n";
620 # Generate a PostScript string
625 my ($l) = length($s);
626 for ( $i = 0 ; $i < $l ; $i++ ) {
627 $c = substr($s,$i,1);
628 if ( ord($c) < 32 || ord($c) > 126 ) {
629 $o .= sprintf("\\%03o", ord($c));
630 } elsif ( $c eq '(' || $c eq ')' || $c eq "\\" ) {
639 # Generate PDF bookmarks
640 print "%%BeginSetup\n";
641 foreach $b ( @bookmarks ) {
642 print '[/Title ', ps_string($b->[2]), "\n";
643 print '/Count ', $b->[1], ' ' if ( $b->[1] );
644 print '/Dest /',$b->[0]," /OUT pdfmark\n";
647 # Ask the PostScript interpreter for the proper size media
648 print "setpagesize\n";
649 print "%%EndSetup\n";
651 # Start a PostScript page
652 sub ps_start_page() {
654 print "%%Page: $ps_page $ps_page\n";
655 print "%%BeginPageSetup\n";
657 print "%%EndPageSetup\n";
660 # End a PostScript page
664 print "($ps_page)", (($ps_page & 1) ? 'pageodd' : 'pageeven'), "\n";
666 print "restore showpage\n";
671 # Title page and inner cover
673 # FIX THIS: This shouldn't be hard-coded like this
674 $title = $metadata{'title'};
675 $title =~ s/ \- / \320 /; # \320 = em dash
676 $pstitle = ps_string($title);
678 lmarg pageheight 2 mul 3 div moveto
679 /Helvetica-Bold findfont 20 scalefont setfont
680 /title linkdest ${pstitle} show
681 lmarg pageheight 2 mul 3 div 10 sub moveto
682 0 setlinecap 3 setlinewidth
683 pagewidth lmarg sub rmarg sub 0 rlineto stroke
687 /Courier-Bold findfont sz scalefont setfont
690 [(-~~..~:\#;L .-:\#;L,.- .~:\#:;.T -~~.~:;. .~:;. )
691 ( E8+U *T +U\' *T\# .97 *L E8+\' *;T\' *;, )
692 ( D97 \`*L .97 \'*L \"T;E+:, D9 *L *L )
693 ( H7 I\# T7 I\# \"*:. H7 I\# I\# )
694 ( U: :8 *\#+ , :8 T, 79 U: :8 :8 )
695 (,\#B. .IE, \"T;E* .IE, J *+;\#:T*\" ,\#B. .IE, .IE,)] {
696 currentpoint 3 -1 roll
697 sz -0.10 mul 0 3 -1 roll ashow
698 sz 0.72 mul sub moveto
703 pagewidth 2 div 143 sub
704 pageheight 2 div 33 add
709 print "% Inner cover goes here\n";
714 for ( $i = 0 ; $i < $nlines ; $i++ ) {
715 my $linfo = $pslines[$i]->[0];
717 if ( $$linfo[4] != $curpage ) {
720 $curpage = $$linfo[4];
725 foreach my $c ( @{$pslines[$i]->[1]} ) {
727 if ( $curfont != $$c[0] ) {
728 print ($curfont = $$c[0]);
730 print ps_string($$c[1]);
731 } elsif ( $$c[0] == -1 ) {
732 print '{el}'; # End link
733 } elsif ( $$c[0] == -2 ) {
734 print '{/',$$c[1],' xl}'; # xref link
735 } elsif ( $$c[0] == -3 ) {
736 print '{',ps_string($$c[1]),'wl}'; # web link
737 } elsif ( $$c[0] == -4 ) {
738 # Index anchor -- ignore
739 } elsif ( $$c[0] == -5 ) {
740 print '{/',$$c[1],' xa}'; #xref anchor
742 die "Unknown annotation";
746 if ( defined($$linfo[2]) ) {
747 foreach my $x ( @{$$linfo[2]} ) {
748 if ( $$x[0] == $AuxStr ) {
749 print ps_string($$x[1]);
750 } elsif ( $$x[0] == $AuxPage ) {
751 print $ps_xref_page{$$x[1]},' ';
752 } elsif ( $$x[0] == $AuxPageStr ) {
753 print ps_string($ps_xref_page{$$x[1]});
754 } elsif ( $$x[0] == $AuxXRef ) {
755 print '/',ps_xref($$x[1]),' ';
756 } elsif ( $$x[0] == $AuxNum ) {
759 die "Unknown auxilliary data type";
763 print ($psconf{pageheight}-$psconf{topmarg}-$$linfo[5]);
764 print ' ', $$linfo[0].$$linfo[1], "\n";
770 # Emit index as comments for now
771 foreach $k ( sort(keys(%ps_index_pages)) ) {
772 print "% ",$k, ' ', join(',', @{$ps_index_pages{$k}});
773 print ' [prefix]' if ( defined($ixhasprefix{$k}) );
774 print ' [first]' if ( defined($ixcommafirst{$k}) );