📄 genps.pl
字号:
my(@ci) = @_;
my($c, $lc);
my(@co, $eco);
undef $lc;
@co = ();
$eco = -1; # Index of the last entry in @co
foreach $c ( @ci ) {
if ( defined($lc) && $$c[0] == $lc && $$c[0] >= 0 ) {
$co[$eco]->[1] .= $$c[1];
} else {
push(@co, $c); $eco++;
$lc = $$c[0];
}
}
return @co;
}
#
# Convert paragraphs to rendering arrays. Each
# element in the array contains (font, string),
# where font can be one of:
# -1 end link
# -2 begin crossref
# -3 begin weblink
# -4 index item anchor
# -5 crossref anchor
# -6 left/right marker (used in the index)
# -7 page link (used in the index)
# 0 normal
# 1 empatic (italic)
# 2 code (fixed spacing)
#
sub mkparaarray($@) {
my($ptype, @chunks) = @_;
my @para = ();
my $in_e = 0;
my $chunk;
if ( $ptype =~ /^code/ ) {
foreach $chunk ( @chunks ) {
push(@para, [2, $chunk]);
}
} else {
foreach $chunk ( @chunks ) {
my $type = substr($chunk,0,2);
my $text = substr($chunk,2);
if ( $type eq 'sp' ) {
push(@para, [$in_e?1:0, ' ']);
} elsif ( $type eq 'da' ) {
push(@para, [$in_e?1:0, $charcode{'endash'}]);
} elsif ( $type eq 'n ' ) {
push(@para, [0, $text]);
$in_e = 0;
} elsif ( $type =~ '^e' ) {
push(@para, [1, $text]);
$in_e = ($type eq 'es' || $type eq 'e ');
} elsif ( $type eq 'c ' ) {
push(@para, [2, $text]);
$in_e = 0;
} elsif ( $type eq 'x ' ) {
push(@para, [-2, ps_xref($text)]);
} elsif ( $type eq 'xe' ) {
push(@para, [-1, undef]);
} elsif ( $type eq 'wc' || $type eq 'w ' ) {
$text =~ /\<(.*)\>(.*)$/;
my $link = $1; $text = $2;
push(@para, [-3, $link]);
push(@para, [($type eq 'wc') ? 2:0, $text]);
push(@para, [-1, undef]);
$in_e = 0;
} elsif ( $type eq 'i ' ) {
push(@para, [-4, $text]);
} else {
die "Unexpected paragraph chunk: $chunk";
}
}
}
return @para;
}
$npara = scalar(@paras);
for ( $i = 0 ; $i < $npara ; $i++ ) {
$paras[$i] = [mkparaarray($ptypes[$i], @{$paras[$i]})];
}
#
# This converts a rendering array to a simple string
#
sub ps_arraytostr(@) {
my $s = '';
my $c;
foreach $c ( @_ ) {
$s .= $$c[1] if ( $$c[0] >= 0 );
}
return $s;
}
#
# This generates a duplicate of a paragraph
#
sub ps_dup_para(@) {
my(@i) = @_;
my(@o) = ();
my($c);
foreach $c ( @i ) {
my @cc = @{$c};
push(@o, [@cc]);
}
return @o;
}
#
# This generates a duplicate of a paragraph, stripping anchor
# tags (-4 and -5)
#
sub ps_dup_para_noanchor(@) {
my(@i) = @_;
my(@o) = ();
my($c);
foreach $c ( @i ) {
my @cc = @{$c};
push(@o, [@cc]) unless ( $cc[0] == -4 || $cc[0] == -5 );
}
return @o;
}
#
# Scan for header paragraphs and fix up their contents;
# also generate table of contents and PDF bookmarks.
#
@tocparas = ([[-5, 'contents'], [0,'Contents']]);
@tocptypes = ('chap');
@bookmarks = (['title', 0, 'Title'], ['contents', 0, 'Contents']);
%bookref = ();
for ( $i = 0 ; $i < $npara ; $i++ ) {
my $xtype = $ptypes[$i];
my $ptype = substr($xtype,0,4);
my $str;
my $book;
if ( $ptype eq 'chap' || $ptype eq 'appn' ) {
unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
die "Bad para";
}
my $secn = $1;
my $sech = $2;
my $xref = ps_xref($sech);
my $chap = ($ptype eq 'chap')?'Chapter':'Appendix';
$book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
push(@bookmarks, $book);
$bookref{$secn} = $book;
push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
push(@tocptypes, 'toc0'.' :'.$sech.':'.$chap.' '.$secn.':');
unshift(@{$paras[$i]},
[-5, $xref], [0,$chap.' '.$secn.':'], [0, ' ']);
} elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
die "Bad para";
}
my $secn = $1;
my $sech = $2;
my $xref = ps_xref($sech);
my $pref;
$pref = $secn; $pref =~ s/\.[^\.]+$//; # Find parent node
$book = [$xref, 0, ps_arraytostr(@{$paras[$i]})];
push(@bookmarks, $book);
$bookref{$secn} = $book;
$bookref{$pref}->[1]--; # Adjust count for parent node
push(@tocparas, [ps_dup_para_noanchor(@{$paras[$i]})]);
push(@tocptypes,
(($ptype eq 'subh') ? 'toc2':'toc1').' :'.$sech.':'.$secn);
unshift(@{$paras[$i]}, [-5, $xref]);
}
}
#
# Add TOC to beginning of paragraph list
#
unshift(@paras, @tocparas); undef @tocparas;
unshift(@ptypes, @tocptypes); undef @tocptypes;
#
# Add copyright notice to the beginning
#
unshift(@paras,
[[0, $charcode{'copyright'}], [0, ' '], [0,$metadata{'year'}],
[0, ' '], string2array($metadata{'author'})],
[string2array($metadata{'license'})]);
unshift(@ptypes, 'norm', 'norm');
$npara = scalar(@paras);
#
# No lines generated, yet.
#
@pslines = ();
#
# Line Auxilliary Information Types
#
$AuxStr = 1; # String
$AuxPage = 2; # Page number (from xref)
$AuxPageStr = 3; # Page number as a PostScript string
$AuxXRef = 4; # Cross reference as a name
$AuxNum = 5; # Number
#
# Break or convert paragraphs into lines, and push them
# onto the @pslines array.
#
sub ps_break_lines($$) {
my ($paras,$ptypes) = @_;
my $linewidth = $psconf{pagewidth}-$psconf{lmarg}-$psconf{rmarg};
my $bullwidth = $linewidth-$psconf{bulladj};
my $indxwidth = ($linewidth-$psconf{idxgutter})/$psconf{idxcolumns}
-$psconf{idxspace};
my $npara = scalar(@{$paras});
my $i;
for ( $i = 0 ; $i < $npara ; $i++ ) {
my $xtype = $ptypes->[$i];
my $ptype = substr($xtype,0,4);
my @data = @{$paras->[$i]};
my @ls = ();
if ( $ptype eq 'code' ) {
my $p;
# Code paragraph; each chunk is a line
foreach $p ( @data ) {
push(@ls, [[$ptype,0,undef,\%BodyFont,0,0],[$p]]);
}
$ls[0]->[0]->[1] |= 1; # First in para
$ls[-1]->[0]->[1] |= 2; # Last in para
} elsif ( $ptype eq 'chap' || $ptype eq 'appn' ) {
# Chapters are flowed normally, but in an unusual font
@ls = ps_flow_lines($linewidth, \%ChapFont, $ptype, @data);
} elsif ( $ptype eq 'head' || $ptype eq 'subh' ) {
unless ( $xtype =~ /^\S+ (\S+) :(.*)$/ ) {
die "Bad para";
}
my $secn = $1;
my $sech = $2;
my $font = ($ptype eq 'head') ? \%HeadFont : \%SubhFont;
@ls = ps_flow_lines($linewidth, $font, $ptype, @data);
# We need the heading number as auxillary data
$ls[0]->[0]->[2] = [[$AuxStr,$secn]];
} elsif ( $ptype eq 'norm' ) {
@ls = ps_flow_lines($linewidth, \%BodyFont, $ptype, @data);
} elsif ( $ptype eq 'bull' ) {
@ls = ps_flow_lines($bullwidth, \%BodyFont, $ptype, @data);
} elsif ( $ptype =~ /^toc/ ) {
unless ( $xtype =~/^\S+ :([^:]*):(.*)$/ ) {
die "Bad para";
}
my $xref = $1;
my $refname = $2.' ';
my $ntoc = substr($ptype,3,1)+0;
my $refwidth = ps_width($refname, $BodyFont{fonts}->[0][1],
\@NASMEncoding) *
($BodyFont{fonts}->[0][0]/1000);
@ls = ps_flow_lines($linewidth-$ntoc*$psconf{tocind}-
$psconf{tocpnz}-$refwidth,
\%BodyFont, $ptype, @data);
# Auxilliary data: for the first line, the cross reference symbol
# and the reference name; for all lines but the first, the
# reference width; and for the last line, the page number
# as a string.
my $nl = scalar(@ls);
$ls[0]->[0]->[2] = [[$AuxStr,$refname], [$AuxXRef,$xref]];
for ( $j = 1 ; $j < $nl ; $j++ ) {
$ls[$j]->[0]->[2] = [[$AuxNum,$refwidth]];
}
push(@{$ls[$nl-1]->[0]->[2]}, [$AuxPageStr,$xref]);
} elsif ( $ptype =~ /^idx/ ) {
my $lvl = substr($ptype,3,1)+0;
@ls = ps_flow_lines($indxwidth-$lvl*$psconf{idxindent},
\%BodyFont, $ptype, @data);
} else {
die "Unknown para type: $ptype";
}
# Merge adjacent identical chunks
foreach $l ( @ls ) {
@{$$l[1]} = ps_merge_chunks(@{$$l[1]});
}
push(@pslines,@ls);
}
}
# Break the main body text into lines.
ps_break_lines(\@paras, \@ptypes);
#
# Break lines in to pages
#
# Where to start on page 2, the copyright page
$curpage = 2; # Start on page 2
$curypos = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg}-
$psconf{startcopyright};
undef $columnstart; # Not outputting columnar text
undef $curcolumn; # Current column
$nlines = scalar(@pslines);
#
# This formats lines inside the global @pslines array into pages,
# updating the page and y-coordinate entries. Start at the
# $startline position in @pslines and go to but not including
# $endline. The global variables $curpage, $curypos, $columnstart
# and $curcolumn are updated appropriately.
#
sub ps_break_pages($$) {
my($startline, $endline) = @_;
# Paragraph types which should never be broken
my $nobreakregexp = "^(chap|appn|head|subh|toc.|idx.)\$";
# Paragraph types which are heading (meaning they should not be broken
# immediately after)
my $nobreakafter = "^(chap|appn|head|subh)\$";
# Paragraph types which should never be broken *before*
my $nobreakbefore = "^idx[1-9]\$";
# Paragraph types which are set in columnar format
my $columnregexp = "^idx.\$";
my $upageheight = $psconf{pageheight}-$psconf{topmarg}-$psconf{botmarg};
my $i;
for ( $i = $startline ; $i < $endline ; $i++ ) {
my $linfo = $pslines[$i]->[0];
if ( ($$linfo[0] eq 'chap' || $$linfo[0] eq 'appn' )
&& ($$linfo[1] & 1) ) {
# First line of a new chapter heading. Start a new page.
undef $columnstart;
$curpage++ if ( $curypos > 0 || defined($columnstart) );
$curypos = $chapstart;
} elsif ( defined($columnstart) && $$linfo[0] !~ /$columnregexp/o ) {
undef $columnstart;
$curpage++;
$curypos = 0;
}
if ( $$linfo[0] =~ /$columnregexp/o && !defined($columnstart) ) {
$columnstart = $curypos;
$curcolumn = 0;
}
# Adjust position by the appropriate leading
$curypos += $$linfo[3]->{leading};
# Record the page and y-position
$$linfo[4] = $curpage;
$$linfo[5] = $curypos;
$$linfo[6] = $curcolumn if ( defined($columnstart) );
if ( $curypos > $upageheight ) {
# We need to break the page before this line.
my $broken = 0; # No place found yet
while ( !$broken && $pslines[$i]->[0]->[4] == $curpage ) {
my $linfo = $pslines[$i]->[0];
my $pinfo = $pslines[$i-1]->[0];
if ( $$linfo[1] == 2 ) {
# This would be an orphan, don't break.
} elsif ( $$linfo[1] & 1 ) {
# Sole line or start of paragraph. Break unless
# the previous line was part of a heading.
$broken = 1 if ( $$pinfo[0] !~ /$nobreakafter/o &&
$$linfo[0] !~ /$nobreakbefore/o );
} else {
# Middle of paragraph. Break unless we're in a
# no-break paragraph, or the previous line would
# end up being a widow.
$broken = 1 if ( $$linfo[0] !~ /$nobreakregexp/o &&
$$pinfo[1] != 1 );
}
$i--;
}
die "Nowhere to break page $curpage\n" if ( !$broken );
# Now $i should point to line immediately before the break, i.e.
# the next paragraph should be the first on the new page
if ( defined($columnstart) &&
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -