summaryrefslogtreecommitdiff
path: root/systems/tex-extensions/clasen/hz
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /systems/tex-extensions/clasen/hz
Initial commit
Diffstat (limited to 'systems/tex-extensions/clasen/hz')
l---------systems/tex-extensions/clasen/hz/etex.bib1
-rw-r--r--systems/tex-extensions/clasen/hz/hz-glue.ch26
-rw-r--r--systems/tex-extensions/clasen/hz/hz-post-web2c.ch76
-rw-r--r--systems/tex-extensions/clasen/hz/hz.ch465
-rw-r--r--systems/tex-extensions/clasen/hz/hz.tex104
5 files changed, 672 insertions, 0 deletions
diff --git a/systems/tex-extensions/clasen/hz/etex.bib b/systems/tex-extensions/clasen/hz/etex.bib
new file mode 120000
index 0000000000..59519b0c14
--- /dev/null
+++ b/systems/tex-extensions/clasen/hz/etex.bib
@@ -0,0 +1 @@
+../etex.bib \ No newline at end of file
diff --git a/systems/tex-extensions/clasen/hz/hz-glue.ch b/systems/tex-extensions/clasen/hz/hz-glue.ch
new file mode 100644
index 0000000000..e38a1f4572
--- /dev/null
+++ b/systems/tex-extensions/clasen/hz/hz-glue.ch
@@ -0,0 +1,26 @@
+@x
+@p function adjust_excess(p:pointer;r:real):scaled;
+@y
+@p function adjust_excess(p:pointer;r:glue_ratio):scaled;
+@z
+
+@x
+ d:=abs(v+char_width(f)(i)-r*w);
+@y
+ d:=abs(v+char_width(f)(i)-glue_mult(r,w));
+@z
+
+@x
+ dd:=abs(v+char_width(ff)(ii)-r*w);
+@y
+ dd:=abs(v+char_width(ff)(ii)-glue_mult(r,w));
+@z
+
+@x
+ x:=x-adjust_excess(link(q),(gw+x)/gw);
+@y
+ { |adjust_excess| tries to make up the excess~|x| by stretching the glyphs
+ by the factor |gw+x/gw| (represented as a |glue_ratio| value). }
+ x:=x-adjust_excess(link(q),glue_fix(gw,gw+x,gw));
+@z
+
diff --git a/systems/tex-extensions/clasen/hz/hz-post-web2c.ch b/systems/tex-extensions/clasen/hz/hz-post-web2c.ch
new file mode 100644
index 0000000000..f6c17e3af6
--- /dev/null
+++ b/systems/tex-extensions/clasen/hz/hz-post-web2c.ch
@@ -0,0 +1,76 @@
+%
+% This is web2c-specific.
+%
+
+@x
+@!font_variants:array[internal_font_number] of min_halfword..max_halfword;
+@y
+@!font_variants:^pointer;
+@z
+
+%
+% web2c moves the initialization to somewhere else.
+%
+
+@x
+font_variants[null_font]:=null;
+@y
+@z
+
+%
+% We must dump the font_variants table as well.
+%
+
+@x
+dump_things(font_glue[null_font], font_ptr+1-null_font);
+@y
+dump_things(font_glue[null_font], font_ptr+1-null_font);
+dump_things(font_variants[null_font], font_ptr+1-null_font);
+@z
+
+%
+% This is web2c-specific.
+%
+
+@x
+xmalloc_array(param_base, font_max);
+@y
+xmalloc_array(param_base, font_max);
+xmalloc_array(font_variants, font_max);
+@z
+
+%
+% We must undump the font_variants table as well.
+%
+
+@x
+undump_checked_things(min_halfword, lo_mem_max,
+ font_glue[null_font], font_ptr+1-null_font);
+@y
+undump_checked_things(min_halfword, lo_mem_max,
+ font_glue[null_font], font_ptr+1-null_font);
+undump_things(font_variants[null_font], font_ptr+1-null_font);
+@z
+
+%
+% This is web2c-specific.
+%
+
+@x
+ xmalloc_array(param_base, font_max);
+@y
+ xmalloc_array(param_base, font_max);
+ xmalloc_array(font_variants, font_max);
+@z
+
+%
+% web2c moves the initialization to somewhere else.
+%
+
+@x
+ param_base[null_font]:=-1;
+@y
+ param_base[null_font]:=-1;
+ font_variants[null_font]:=null;
+@z
+
diff --git a/systems/tex-extensions/clasen/hz/hz.ch b/systems/tex-extensions/clasen/hz/hz.ch
new file mode 100644
index 0000000000..b2b2d9f6f5
--- /dev/null
+++ b/systems/tex-extensions/clasen/hz/hz.ch
@@ -0,0 +1,465 @@
+@x
+{hz int_pars go here}
+@y
+@d hz_state_code=80
+@z
+
+@x
+@d error_context_lines==int_par(error_context_lines_code)
+@y
+@d hz_state==int_par(hz_state_code)
+@d hz_en==(hz_state>0)
+@d error_context_lines==int_par(error_context_lines_code)
+@z
+
+@x
+error_context_lines_code:print_esc("errorcontextlines");
+@y
+error_context_lines_code:print_esc("errorcontextlines");
+hz_state_code:print_esc("hzstate");
+@z
+
+@x
+primitive("errorcontextlines",assign_int,int_base+error_context_lines_code);@/
+@!@:error_context_lines_}{\.{\\errorcontextlines} primitive@>
+@y
+primitive("errorcontextlines",assign_int,int_base+error_context_lines_code);@/
+@!@:error_context_lines_}{\.{\\errorcontextlines} primitive@>
+primitive("hzstate",assign_int,int_base+hz_state_code);@/
+@!@:hz_state_}{\.{\\hzstate} primitive@>
+@z
+
+@x
+escape_char:="\"; end_line_char:=carriage_return;
+@y
+escape_char:="\"; end_line_char:=carriage_return;
+hz_state:=0;
+@z
+
+@x
+primitive("font",def_font,0);@/
+@!@:font_}{\.{\\font} primitive@>
+@y
+primitive("font",def_font,0);@/
+@!@:font_}{\.{\\font} primitive@>
+primitive("fontvariant",def_font,1);@/
+@!@:fontvariant_}{\.{\\fontvariant} primitive@>
+@z
+
+@x
+def_font: print_esc("font");
+@y
+def_font: if chr_code=0 then print_esc("font") else print_esc("fontvariant");
+@z
+
+@x l.8326
+@p procedure scan_something_internal(@!level:small_number;@!negative:boolean);
+ {fetch an internal parameter}
+var m:halfword; {|chr_code| part of the operand token}
+@y
+@p procedure scan_something_internal(@!level:small_number;@!negative:boolean);
+ {fetch an internal parameter}
+var m:halfword; {|chr_code| part of the operand token}
+r:pointer; {used with font variants}
+@z
+
+@x l.8379
+else begin back_input; scan_font_ident;
+ scanned_result(font_id_base+cur_val)(ident_val);
+ end
+@y
+else begin
+ if m=0 then begin
+ back_input; scan_font_ident;
+ scanned_result(font_id_base+cur_val)(ident_val);
+ end else if hz_en then begin
+ scan_font_ident;
+ r:=font_variants[cur_val]; cur_val:=0;
+ while r<>null do begin
+ incr(cur_val); r:=link(r);
+ end;
+ scanned_result(cur_val)(int_val);
+ end else begin
+ print_err("Improper "); print_cmd_chr(def_font,m);
+ error;
+ end;
+end
+@z
+
+@x l.10710
+@!font_false_bchar:array[internal_font_number] of min_quarterword..non_char;
+ {|font_bchar| if it doesn't exist in the font, otherwise |non_char|}
+@y
+@!font_false_bchar:array[internal_font_number] of min_quarterword..non_char;
+ {|font_bchar| if it doesn't exist in the font, otherwise |non_char|}
+@!font_variants:array[internal_font_number] of min_halfword..max_halfword;
+@z
+
+@x l.10762
+for k:=0 to 6 do font_info[k].sc:=0;
+@y
+for k:=0 to 6 do font_info[k].sc:=0;
+font_variants[null_font]:=null;
+@z
+
+@x l.11189
+fmem_ptr:=fmem_ptr+lf; font_ptr:=f; g:=f; goto done
+@y
+fmem_ptr:=fmem_ptr+lf; font_ptr:=f; g:=f;
+for a:=0 to 255 do begin
+ qw:=char_info(f)(a);
+ if char_exists(qw) then begin
+ font_variants[f]:=get_avail;
+ font(font_variants[f]):=f;
+ character(font_variants[f]):=a;
+ link(font_variants[f]):=null;
+ goto done;
+ end;
+end;
+goto done;
+@z
+
+@x l.12860
+@ Here now is |hpack|, which contains few if any surprises.
+
+@p function hpack(@!p:pointer;@!w:scaled;@!m:small_number):pointer;
+label reswitch, common_ending, exit;
+var r:pointer; {the box node that will be returned}
+@!q:pointer; {trails behind |p|}
+@!h,@!d,@!x:scaled; {height, depth, and natural width}
+@y
+@ This routine replaces characters in the hlist |p| with variants from other
+fonts in order to stretch the natural width by |r|. It returns the amount by
+which the natural width could be stretched.
+
+@p function adjust_excess(p:pointer;r:real):scaled;
+label reswitch,found,done;
+var v,w,d,dd:scaled;
+f,ff,c:eight_bits;
+i,ii:four_quarters;
+s:pointer;
+j:integer;
+begin
+@!debug
+print("(adjust_excess: "); print_scaled(round(unity*r));
+print(" --> ");
+gubed
+v:=0; w:=0;
+while p<>null do begin
+ reswitch: while is_char_node(p) do begin
+ c:=character(p); f:=font(p); i:=char_info(f)(c);
+ w:=w+char_width(f)(i);
+ s:=font_variants[f];
+ f:=font(s); i:=char_info(f)(c);
+ d:=abs(v+char_width(f)(i)-r*w);
+ s:=link(s);
+@!debug
+ j:=0;
+gubed
+ while s<>null do begin
+ ff:=font(s); ii:=char_info(ff)(c);
+ dd:=abs(v+char_width(ff)(ii)-r*w);
+ if dd>=d then goto found;
+ d:=dd; f:=ff; i:=ii;
+@!debug
+ j:=j+1;
+gubed
+ s:=link(s);
+ end;
+ found:
+ font(p):=f;
+@!debug
+ print_int(j);
+gubed
+ v:=v+char_width(f)(i);
+ p:=link(p);
+ end;
+ if p=null then goto done;
+ if type(p)=ligature_node then
+ @<Make node |p| look like a |char_node| and |goto reswitch|@>
+ else p:=link(p);
+end;
+done:
+@!debug
+print_ln;
+print(" got "); print_scaled(v-w); print(")");
+gubed
+adjust_excess:=v-w;
+end;
+
+@ Here now is |hpack|, which contains few if any surprises.
+
+@p function hpack(@!p:pointer;@!w:scaled;@!m:small_number):pointer;
+label reswitch, common_ending, exit;
+var r:pointer; {the box node that will be returned}
+@!q:pointer; {trails behind |p|}
+@!h,@!d,@!x:scaled; {height, depth, and natural width}
+@!gw:scaled; {natural width coming from glyphs}
+@z
+
+@x
+h:=0; @<Clear dimensions to zero@>;
+@y
+h:=0; gw:=0; @<Clear dimensions to zero@>;
+@z
+
+@x
+@<Incorporate character dimensions into the dimensions of the hbox...@>=
+begin f:=font(p); i:=char_info(f)(character(p)); hd:=height_depth(i);
+x:=x+char_width(f)(i);@/
+@y
+@<Incorporate character dimensions into the dimensions of the hbox...@>=
+begin f:=font(p); i:=char_info(f)(character(p)); hd:=height_depth(i);
+x:=x+char_width(f)(i);@/
+gw:=gw+char_width(f)(i);
+@z
+
+@x
+else if x>0 then @<Determine horizontal glue stretch setting, then |return|
+ or \hbox{|goto common_ending|}@>
+else @<Determine horizontal glue shrink setting, then |return|
+ or \hbox{|goto common_ending|}@>
+@y
+else begin
+ if hz_en then begin
+ if gw<>0 then begin
+ x:=x-adjust_excess(link(q),(gw+x)/gw);
+ end;
+ end;
+ if x>0 then @<Determine horizontal glue stretch setting, then |return|
+ or \hbox{|goto common_ending|}@>
+ else @<Determine horizontal glue shrink setting, then |return|
+ or \hbox{|goto common_ending|}@>;
+end
+@z
+
+@x
+@d delta_node_size=7 {number of words in a delta node}
+@d delta_node=2 {|type| field in a delta node}
+@y
+@d delta_node_size=9 {number of words in a delta node}
+@d delta_node=2 {|type| field in a delta node}
+@z
+
+@x
+@d do_all_six(#)==#(1);#(2);#(3);#(4);#(5);#(6)
+
+@<Glo...@>=
+@!active_width:array[1..6] of scaled;
+ {distance from first active node to~|cur_p|}
+@!cur_active_width:array[1..6] of scaled; {distance from current active node}
+@!background:array[1..6] of scaled; {length of an ``empty'' line}
+@!break_width:array[1..6] of scaled; {length being computed after current break}
+@y
+For the hz algorithm, we add two more fields to store the finite
+stretch and shrink from glyphs.
+
+@d do_all_six(#)==#(1);#(2);#(3);#(4);#(5);#(6)
+@d do_all_eight(#)==do_all_six(#);#(7);#(8)
+
+@<Glo...@>=
+@!active_width:array[1..8] of scaled;
+ {distance from first active node to~|cur_p|}
+@!cur_active_width:array[1..8] of scaled; {distance from current active node}
+@!background:array[1..8] of scaled; {length of an ``empty'' line}
+@!break_width:array[1..8] of scaled; {length being computed after current break}
+@z
+
+@x
+background[6]:=shrink(q)+shrink(r);
+@y
+background[6]:=shrink(q)+shrink(r);
+background[7]:=0; background[8]:=0;
+@z
+
+@x
+do_all_six(copy_to_cur_active);
+@y
+do_all_eight(copy_to_cur_active);
+@z
+
+@x
+ begin do_all_six(update_width);
+@y
+ begin do_all_eight(update_width);
+@z
+
+@x
+begin no_break_yet:=false; do_all_six(set_break_width_to_background);
+@y
+begin no_break_yet:=false; do_all_eight(set_break_width_to_background);
+@z
+
+@x
+ begin do_all_six(convert_to_break_width);
+@y
+ begin do_all_eight(convert_to_break_width);
+@z
+
+@x
+ begin do_all_six(store_break_width);
+@y
+ begin do_all_eight(store_break_width);
+@z
+
+@x
+ do_all_six(new_delta_to_break_width);
+@y
+ do_all_eight(new_delta_to_break_width);
+@z
+
+@x
+ do_all_six(new_delta_from_break_width);
+@y
+ do_all_eight(new_delta_from_break_width);
+@z
+
+@x
+shortfall:=line_width-cur_active_width[1]; {we're this much too short}
+@y
+if hz_en then begin
+ if cur_active_width[1]+cur_active_width[7]<line_width then
+ shortfall:=line_width-(cur_active_width[1]+cur_active_width[7])
+ else if cur_active_width[1]-cur_active_width[8]>line_width then
+ shortfall:=line_width-(cur_active_width[1]-cur_active_width[8])
+ else shortfall:=0;
+end else shortfall:=line_width-cur_active_width[1]; {we're this much too short}
+@z
+
+@x
+ begin do_all_six(downdate_width);
+@y
+ begin do_all_eight(downdate_width);
+@z
+
+@x
+ begin do_all_six(update_width);
+ do_all_six(combine_two_deltas);
+@y
+ begin do_all_eight(update_width);
+ do_all_eight(combine_two_deltas);
+@z
+
+@x
+ begin do_all_six(update_active);
+ do_all_six(copy_to_cur_active);
+@y
+ begin do_all_eight(update_active);
+ do_all_eight(copy_to_cur_active);
+@z
+
+@x
+do_all_six(store_background);@/
+@y
+do_all_eight(store_background);@/
+@z
+
+@x
+begin prev_p:=cur_p;
+repeat f:=font(cur_p);
+act_width:=act_width+char_width(f)(char_info(f)(character(cur_p)));
+cur_p:=link(cur_p);
+until not is_char_node(cur_p);
+end
+@y
+begin prev_p:=cur_p;
+repeat f:=font(cur_p);
+w:=char_width(f)(char_info(f)(character(cur_p)));
+act_width:=act_width+w;
+if hz_en then @<Update stretch and shrink in the presence of font variants@>;
+cur_p:=link(cur_p);
+until not is_char_node(cur_p);
+end
+
+@ @<Local variables for line...@>=
+@!w:scaled; {used when calculating character widths}
+
+@ @<Update stretch and shrink...@>=
+begin
+r:=font_variants[f];
+active_width[7]:=active_width[7]+
+ char_width(font(r))(char_info(font(r))(character(cur_p)))-w;
+while link(r)<>null do r:=link(r);
+active_width[8]:=active_width[8]+
+ w-char_width(font(r))(char_info(font(r))(character(cur_p)));
+end
+@z
+
+@x
+def_font: new_font(a);
+@y
+def_font: if cur_chr=0 then new_font(a) else
+ if hz_en then font_variant(a)
+ else begin print_err("Improper "); print_cmd_chr(def_font,cur_chr); error; end;
+
+
+@ @<Declare subprocedures for |prefixed_command|@>=
+procedure font_variant(@!a:small_number);
+label found,done;
+var @!f,ff:internal_font_number;
+@!i:eight_bits;
+@!qw,qww:four_quarters;
+@!r,s,p: pointer;
+begin
+ scan_font_ident; f:=cur_val;
+ scan_optional_equals; scan_font_ident; ff:=cur_val;
+ @<If |ff| is |null_font|, reset the list of variants and goto |found|@>;
+ @<Check that |ff| is a valid variant of |f|, else goto |done|@>;
+ s:=null; r:=font_variants[f];
+ i:=character(r);
+ p:=new_character(ff,i);
+ qww:=char_info(ff)(i);
+ while r<>null do begin
+ if font(r)=ff then goto done;
+ qw:=char_info(font(r))(i);
+ if char_width(font(r))(qw)<char_width(ff)(qww) then goto found;
+ s:=r; r:=link(r);
+ end;
+found:
+ link(p):=r;
+ if s=null then font_variants[f]:=p
+ else link(s):=p;
+ done:
+@!debug
+ print("(fontvariants of ");
+ print(font_name[f]); print(": ");
+ r:=font_variants[f];
+ while r<>null do begin
+ print(font_name[font(r)]);
+ r:=link(r);
+ if r<>null then print(", ");
+ end;
+ print(")");
+gubed
+end;
+
+@ Fonts can only be variants of each other if they provide the
+same characters.
+
+@<Check that |ff| is a valid variant of |f|...@>=
+for i:=0 to 255 do begin
+ qw:=char_info(f)(i);
+ qww:=char_info(f)(i);
+ if (char_exists(qw) and not char_exists(qww)) or
+ (not char_exists(qw) and char_exists(qww)) then begin
+ print_err("Font "); print_esc(font_id_text(ff));
+ print(" is not a variant of font "); print_esc(font_id_text(f));
+@.Font x is not a...@>
+ help2("Fonts can only be used as variants of each other")@/
+ ("if they contain the same characters.");
+ error;
+ goto done;
+ end;
+end
+
+@ The only way to remove variants of a font is to assign |null_font|
+as a variant.
+
+@<If |ff| is |null_font|...@>=
+if ff=null_font then begin
+ r:=null; s:=null; p:=new_character(f,character(font_variants[f]));
+ flush_node_list(font_variants[f]);
+ goto found;
+end
+@z
+
diff --git a/systems/tex-extensions/clasen/hz/hz.tex b/systems/tex-extensions/clasen/hz/hz.tex
new file mode 100644
index 0000000000..3ff4432261
--- /dev/null
+++ b/systems/tex-extensions/clasen/hz/hz.tex
@@ -0,0 +1,104 @@
+\documentclass[preprint]{ltugboat}
+\usepackage{shortvrb}
+\usepackage{url}
+
+\providecommand{\meta}[1]{\ensuremath{\langle\textit{#1}\rangle}}
+\makeatletter
+\providecommand{\eTeX}{$\m@th\varepsilon$-\TeX}
+\makeatother
+\hyphenation{pa-ra-me-ter}
+
+\emergencystretch=4mm
+\overfullrule=0pt
+\title{Why \TeX\ can't set the Gutenberg bible}
+\author{Matthias Clasen}
+%\address{}
+\netaddress{maclas@gmx.de}
+\setcounter{page}{1}
+
+\begin{document}
+\maketitle
+\MakeShortVerb{\|}
+
+\section{Introduction}
+
+\TeX's paragraph breaking algorithm is one of its virtues. It usually
+achieves a fairly uniform grey value throughout the page. If it can't achieve
+completely uniform spacing, the excess space is distributed among the
+interword spaces in the line.
+
+Thus \TeX\ doesn't use one technique for uniform spacing which was invented
+as far back as Gutenberg: variation of the letter width to make up excess
+space.
+
+I propose a minimalist extension of \TeX\ to enable it to use letters of
+variable width. The extension is to associate \textit{variants} with
+fonts. If \TeX\ is about to set character~|c| from font~|f|, it looks for
+character~|c| in all variants associated with~|f| and chooses the one
+which ``fits best in the current situation''. I.\,e.\ in a tight line,
+a narrow variant will be chosen, while a wide variant would be preferable
+in a loose line. The variability of the character width also influences the
+calculation of the badness during the paragraph breaking. This is the true
+strength of the extension: like |\emergencystretch|, it adds flexibility
+during paragraph breaking and thus increases the number of feasible
+breakpoints, but without tearing ugly holes in the shape of the paragraph.
+
+I call this a minimalist extension, because it completely ignores the
+questions of suitable font technology for automatic generation of font
+variants from existing fonts. From the point of view taken here, font
+variants are just separate |tfm| files which are only required to contain
+characters in the same positions. Furthermore \TeX\ assumes that the
+widths vary consistently between the variants, i.\,e.\ if variant~|f|
+contains \emph{one} character which is wider than the corresponding
+character in variant~|f'|, then \emph{all} characters from~|f| should
+be at least as wide as their counterparts in~|f'|.
+
+\section{Implementation}
+
+The changes to \TeX{} the program sketched above are realized in the
+change file |hz.ch|. It implements the new primitive
+\[\cs{fontvariant}\meta{font$_1$}=\meta{font$_2$}\]
+which declares \meta{font$_2$} to be a variant of \meta{font$_1$}
+(where \meta{font$_1$} and \meta{font$_2$} must be font identifiers).
+Font variants can only be removed \textit{en bloc}, by using
+|\nullfont| as \meta{font$_2$}.
+
+\TeX\ will use the fontvariants and allow the |\fontvariant| primitive only
+when the new integer parameter |\hzstate| has a positive value. Setting
+|\hzstate| to zero does \emph{not} remove the font variants previously
+declared.
+
+\TeX\ uses the font variants only when it looks for the width of a character.
+All other |tfm| informations, in particular kerning and ligaturing, are always
+taken from the base font.
+
+\section{Installation}
+
+ The change files are written for \TeX~3.1459. But it should need only
+ cosmetic changes to get them to work with other versions of \TeX3.
+
+ To make the change files for \TeX{} work, you have to apply the change file
+ |pre.ch| first. I have only tried these files with |web2c|, and
+ for that you need to surround the |web2c| change file with |pre-web2c.ch| and
+ |post-web2c.ch|. You also need the additional change file
+ |hz-post-web2c.ch|. If you want to use this change together with the
+ change for fixed-point arithmetic (|glue.ch|), then you also need
+ the file |hz-glue.ch|.
+
+ All this boils down to the command line
+
+\begin{verbatim}
+mv tex.ch tex.ch.orig
+tie -c tex.ch tex.web pre.ch hz.ch \
+ hz-glue.ch pre-web2c.ch \
+ tex.ch.orig post-web2c.ch \
+ hz-post-web2c.ch
+\end{verbatim}
+
+ \makesignature
+
+ \bibliographystyle{plain}
+ \bibliography{etex}
+
+\end{document}
+