summaryrefslogtreecommitdiff
path: root/systems/tex-extensions/clasen/margin
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/margin
Initial commit
Diffstat (limited to 'systems/tex-extensions/clasen/margin')
l---------systems/tex-extensions/clasen/margin/etex.bib1
-rw-r--r--systems/tex-extensions/clasen/margin/margin-post-web2c.ch45
-rw-r--r--systems/tex-extensions/clasen/margin/margin.ch341
-rw-r--r--systems/tex-extensions/clasen/margin/margin.tex97
4 files changed, 484 insertions, 0 deletions
diff --git a/systems/tex-extensions/clasen/margin/etex.bib b/systems/tex-extensions/clasen/margin/etex.bib
new file mode 120000
index 0000000000..59519b0c14
--- /dev/null
+++ b/systems/tex-extensions/clasen/margin/etex.bib
@@ -0,0 +1 @@
+../etex.bib \ No newline at end of file
diff --git a/systems/tex-extensions/clasen/margin/margin-post-web2c.ch b/systems/tex-extensions/clasen/margin/margin-post-web2c.ch
new file mode 100644
index 0000000000..2329dae6fb
--- /dev/null
+++ b/systems/tex-extensions/clasen/margin/margin-post-web2c.ch
@@ -0,0 +1,45 @@
+@x
+@!margin_char:array[internal_font_number] of integer;
+@y
+@!margin_char:^integer;
+@z
+
+@x
+margin_char[null_font]:=-1;
+@y
+@z
+
+@x
+dump_things(skew_char[null_font], font_ptr+1-null_font);
+@y
+dump_things(skew_char[null_font], font_ptr+1-null_font);
+dump_things(margin_char[null_font], font_ptr+1-null_font);
+@z
+
+@x
+xmalloc_array(skew_char, font_max);
+@y
+xmalloc_array(skew_char, font_max);
+xmalloc_array(margin_char, font_max);
+@z
+
+@x
+undump_things(skew_char[null_font], font_ptr+1-null_font);
+@y
+undump_things(skew_char[null_font], font_ptr+1-null_font);
+undump_things(margin_char[null_font], font_ptr+1-null_font);
+@z
+
+@x
+ xmalloc_array(skew_char, font_max);
+@y
+ xmalloc_array(skew_char, font_max);
+ xmalloc_array(margin_char, font_max);
+@z
+
+@x
+ hyphen_char[null_font]:="-"; skew_char[null_font]:=-1;
+@y
+ hyphen_char[null_font]:="-"; skew_char[null_font]:=-1;
+ margin_char[null_font]:=-1;
+@z \ No newline at end of file
diff --git a/systems/tex-extensions/clasen/margin/margin.ch b/systems/tex-extensions/clasen/margin/margin.ch
new file mode 100644
index 0000000000..d8666a73da
--- /dev/null
+++ b/systems/tex-extensions/clasen/margin/margin.ch
@@ -0,0 +1,341 @@
+@x
+{margin int_pars go here}
+@y
+@d default_margin_char_code=81
+@z
+
+@x
+@d error_context_lines==int_par(error_context_lines_code)
+@y
+@d error_context_lines==int_par(error_context_lines_code)
+@d default_margin_char==int_par(default_margin_char_code)
+@z
+
+@x
+error_context_lines_code:print_esc("errorcontextlines");
+@y
+error_context_lines_code:print_esc("errorcontextlines");
+default_margin_char_code:print_esc("defaultmarginchar");
+@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("defaultmarginchar",assign_int,int_base+default_margin_char_code);@/
+@!@:default_margin_char_}{\.{\\defaultmarginchar} primitive@>
+@z
+
+@x
+@!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|}
+@!margin_char:array[internal_font_number] of integer;
+ {current \.{\\marginchar} values}
+@z
+
+@x
+for k:=0 to 6 do font_info[k].sc:=0;
+@y
+for k:=0 to 6 do font_info[k].sc:=0;
+margin_char[null_font]:=-1;
+@z
+
+@x
+hyphen_char[f]:=default_hyphen_char; skew_char[f]:=default_skew_char;
+@y
+hyphen_char[f]:=default_hyphen_char; skew_char[f]:=default_skew_char;
+margin_char[f]:=default_margin_char;
+@z
+
+@x
+@d copy_to_cur_active(#)==cur_active_width[#]:=active_width[#]
+@d deactivate=60 {go here when node |r| should be deactivated}
+
+@<Declare subprocedures for |line_break|@>=
+@y
+The function |kerning| returns the kerning between the characters
+|cl| and |cr| in font |f|, or zero if there is none.
+
+@d copy_to_cur_active(#)==cur_active_width[#]:=active_width[#]
+@d deactivate=60 {go here when node |r| should be deactivated}
+
+@<Declare subprocedures for |line_break|@>=
+function kerning(f,cl,cr:eight_bits):scaled;
+label done;
+var s:scaled;
+i:four_quarters;
+a:integer;
+begin
+ s:=0;
+ i:=char_info(f)(cl);
+ if char_tag(i)=lig_tag then begin
+ a:=lig_kern_start(f)(i);
+ i:=font_info[a].qqqq;
+ if skip_byte(i)>stop_flag then begin
+ a:=lig_kern_restart(f)(i);
+ i:=font_info[a].qqqq;
+ end;
+ loop@+ begin
+ if next_char(i)=cr then begin
+ if op_byte(i)>=kern_flag then
+ if skip_byte(i)<=stop_flag then s:=char_kern(f)(i);
+ goto done;
+ end;
+ if skip_byte(i)>=stop_flag then goto done;
+ a:=a+qo(skip_byte(i))+1;
+ i:=font_info[a].qqqq;
+ end;
+ end;
+done: kerning:=s;
+end;
+
+@ The function |margin_correction| expects an active node |r| and
+a |cur_p| pointing directly into the horizontal list with the tentative
+line to be broken extending from |r| to |cur_p|.
+
+It returns the sum of the margin corrections for the line between them. It
+does so by finding the first node in the line, skipping everything that would
+be dropped after a break (including a possible |\parindent| box), and
+calculating the kern between the |marginchar| and it. Similarly, the last node
+of the line is used to find the kern between it and the |marginchar|. This
+routine could be made more efficient by avoiding the list traversal. The main
+loop of |try_break| could remember the last node before |cur_p|.
+
+@<Declare subprocedures for |line_break|@>=
+function margin_correction(r,cur_p:pointer):scaled;
+label done;
+var left_kern,right_kern:scaled;
+ sl,s:pointer;
+ f,c:eight_bits;
+ ii:four_quarters;
+ rc,a:integer;
+begin
+ left_kern:=0;
+ right_kern:=0;
+ if break_node(r)=null then begin
+ sl:=temp_head;
+ if (type(link(sl))=hlist_node)and
+ (list_ptr(link(sl))=null) then begin
+ sl:=link(sl); { skip |\parindent| box }
+ end;
+ end else sl:=cur_break(break_node(r));
+ s:=sl;
+ if type(s)=disc_node then begin
+ if post_break(s)<>null then s:=post_break(s)
+ else begin
+ rc:=replace_count(s);
+ s:=link(s);
+ while rc>0 do begin
+ if link(s)<>null then s:=link(s);
+ decr(rc);
+ end;
+ end;
+ end;
+ f:=null_font; c:=qi(0);
+ while s<>null do begin
+ if is_char_node(s) then begin
+ f:=font(s); c:=character(s);
+ s:=null;
+ end else case type(s) of
+ glue_node: s:=link(s);
+ penalty_node: s:=link(s);
+ ligature_node: begin
+ f:=font(lig_char(s));
+ c:=character(lig_char(s));
+ s:=null;
+ end;
+ math_node: s:=link(s);
+ kern_node: if subtype(s)<>explicit then s:=null else s:=link(s);
+ othercases s:=null;
+ endcases;@/
+ end;
+ { if we reach this point, |(f,c)| is the font-char pair starting the line }
+ if (0<=margin_char[f])and(margin_char[f]<=255) then begin
+ left_kern:=kerning(f,qi(margin_char[f]),c);
+ end;
+
+ s:=null;
+ if cur_p then
+ if type(cur_p)=disc_node then begin
+ if pre_break(cur_p)<>null then begin
+ s:=pre_break(cur_p);
+ while link(s)<>null do s:=link(s); { unnecessary list traversal! }
+ goto done;
+ end;
+ end;
+ s:=sl;
+ while link(s)<>cur_p do begin
+ s:=link(s);
+ if s=null then goto done;
+ end;
+done:
+ f:=null_font; c:=qi(0);
+ if s<>null then if is_char_node(s) then begin
+ f:=font(s); c:=character(s);
+ end else if type(s)=ligature_node then begin
+ f:=font(lig_char(s));
+ c:=character(lig_char(s));
+ end;
+ { if we reach this point, |(f,c)| is the font-char pair ending the line }
+ if (0<=margin_char[f])and(margin_char[f]<=255) then begin
+ right_kern:=kerning(f,c,qi(margin_char[f]));
+ end;
+ margin_correction:=left_kern+right_kern;
+end;
+@z
+
+@x
+@!line_width:scaled; {the current line will be justified to this width}
+@y
+@!line_width:scaled; {the current line will be justified to this width}
+@!local_line_width:scaled; {|line_width| including |marginchar| kerning}
+@z
+
+@x
+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}
+@y
+local_line_width:=line_width-margin_correction(r,cur_p);
+if hz_en then begin
+ if cur_active_width[1]+cur_active_width[7]<local_line_width then
+ shortfall:=line_width-(cur_active_width[1]+cur_active_width[7])
+ else if cur_active_width[1]-cur_active_width[8]>local_line_width then
+ shortfall:=line_width-(cur_active_width[1]-cur_active_width[8])
+ else shortfall:=0;
+end else shortfall:=local_line_width-cur_active_width[1]; {we're this much too short}
+@z
+
+@x
+procedure post_line_break(@!final_widow_penalty:integer);
+label done,done1;
+var q,@!r,@!s:pointer; {temporary registers for list manipulation}
+@y
+procedure post_line_break(@!final_widow_penalty:integer);
+label done,done1,done2,done3;
+var q,@!r,@!s:pointer; {temporary registers for list manipulation}
+f,c:eight_bits;
+ss:scaled;
+@z
+
+@x
+@<Modify the end of the line to reflect the nature of the break and to include
+ \.{\\rightskip}; also set the proper value of |disc_break|@>;
+@<Put the \(l)\.{\\leftskip} glue at the left and detach this line@>;
+@y
+@<Modify the end of the line to reflect the nature of the break and to include
+ \.{\\rightskip}; also set the proper value of |disc_break|@>;
+@<Put the \(l)\.{\\leftskip} glue at the left and detach this line@>;
+@<Deal with the \.{\\marginchar} kerning@>;
+@z
+
+@x
+@<Put the \(l)\.{\\leftskip} glue at the left...@>=
+r:=link(q); link(q):=null; q:=link(temp_head); link(temp_head):=r;
+if left_skip<>zero_glue then
+ begin r:=new_param_glue(left_skip_code);
+ link(r):=q; q:=r;
+ end
+@y
+We change this code to always insert the \.{\\leftskip}.
+
+@<Put the \(l)\.{\\leftskip} glue at the left...@>=
+r:=link(q); link(q):=null; q:=link(temp_head); link(temp_head):=r;
+r:=new_param_glue(left_skip_code);
+link(r):=q; q:=r
+
+@ When we reach this code, |q| points to the line in question. The line starts
+with \.{\\leftskip} glue and ends with \.{\\rightskip} glue. If the first node
+after the |leftskip| is a character~|c| whose font has a |marginchar|~|m|, we
+insert the kerning |m|--|c| between the |leftskip| node and charnode~|c|.
+
+If the last node before the |rightskip is a character~|c'| whose font has a
+|marginchar|~|m'|, we insert the kerning |c'|--|m'| between charnode~|c'|
+and the |rightskip| node.
+
+@<Deal with the \.{\\marginchar} kerning@>=
+s:=q; r:=link(s);
+if cur_line=prev_graf+1 then begin {treat the first line specially}
+ if (type(r)=hlist_node)and(list_ptr(r)=null) then begin {skip |parindent| box}
+ s:=r; r:=link(s);
+ end;
+end;
+ss:=0;
+if is_char_node(r) then begin
+ f:=font(r); c:=character(r);
+end else if type(r)=ligature_node then begin
+ f:=font(lig_char(r)); c:=character(lig_char(r));
+end else goto done2;
+if (0<=margin_char[f])and(margin_char[f]<=255) then begin
+ ss:=kerning(f,qi(margin_char[f]),c);
+end;
+done2:
+if ss<>0 then begin
+ s:=new_kern(ss); link(q):=s; link(s):=r;
+ s:=link(q);
+end;
+while link(r)<>null do begin
+ s:=r; r:=link(r); { unnecessary list traversal! }
+end;
+ss:=0;
+if is_char_node(s) then begin
+ f:=font(s); c:=character(s);
+end else if type(s)=ligature_node then begin
+ f:=font(lig_char(s)); c:=character(lig_char(s));
+end else goto done3;
+if (0<=margin_char[f])and(margin_char[f]<=255) then begin
+ ss:=kerning(f,c,qi(margin_char[f]));
+end;
+done3:
+if ss<>0 then begin
+ link(s):=new_kern(ss);
+ link(link(s)):=r;
+end;
+@z
+
+@x
+assign_font_int: begin n:=cur_chr; scan_font_ident; f:=cur_val;
+ scan_optional_equals; scan_int;
+ if n=0 then hyphen_char[f]:=cur_val@+else skew_char[f]:=cur_val;
+ end;
+@y
+assign_font_int: begin n:=cur_chr; scan_font_ident; f:=cur_val;
+ scan_optional_equals; scan_int;
+ case n of
+ 0: hyphen_char[f]:=cur_val;
+ 1: skew_char[f]:=cur_val;
+ othercases margin_char[f]:=cur_val;
+ endcases;
+ end;
+@z
+
+@x
+primitive("skewchar",assign_font_int,1);
+@!@:skew_char_}{\.{\\skewchar} primitive@>
+@y
+primitive("skewchar",assign_font_int,1);
+@!@:skew_char_}{\.{\\skewchar} primitive@>
+primitive("marginchar",assign_font_int,2);
+@!@:margin_char_}{\.{\\marginchar} primitive@>
+@z
+
+@x
+assign_font_int: if chr_code=0 then print_esc("hyphenchar")
+ else print_esc("skewchar");
+@y
+assign_font_int: case chr_code of
+ 0: print_esc("hyphenchar");
+ 1: print_esc("skewchar");
+ othercases print_esc("marginchar");
+ endcases;
+@z
+
diff --git a/systems/tex-extensions/clasen/margin/margin.tex b/systems/tex-extensions/clasen/margin/margin.tex
new file mode 100644
index 0000000000..561ab8514f
--- /dev/null
+++ b/systems/tex-extensions/clasen/margin/margin.tex
@@ -0,0 +1,97 @@
+\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
+\title{Hanging punctuation revisited}
+\author{Matthias Clasen}
+%\address{}
+\netaddress{maclas@gmx.de}
+\setcounter{page}{1}
+
+\begin{document}
+\maketitle
+\MakeShortVerb{\|}
+
+\section{Introduction}
+
+In appendix D of the \TeX book~\cite{Knuth:86}, Knuth discusses a way to
+implement hanging punctuation within \TeX. As noted by Frank
+Mittelbach~\cite{Mittelbach:90}, this solution \textit{make[s] the ligature
+ mechanism partly unusable, along with other side effects,}
+and thus a better solution is needed.
+
+Since the problem obviously depends on the shape of the letters, it seems
+natural to attack it at the font level. The basic idea is to associate
+a |\marginchar| with each font. The kerning with the |\marginchar| (at the
+appropriate side) is used to determine how far the first/last character on
+a line should drop in the margin. This information is used in the calculation
+of the length (and badness) of a ``candidate line'' during paragraph breaking.
+
+Every font in \TeX\ has an associated |\margin|\-|char| parameter (analogous
+to |\hyphenchar| and |\skewchar|) whose initial value is the value of
+|\default|\-|margin|\-|char|. The |tfm| file doesn't have to know anything
+about |\marginchar|s, it just has to contain appropriate kern values. It is
+perfectly possible to have more than one |\marginchar| in a |tfm| file,
+e.\,g.\ for
+\begin{itemize}
+\item hanging punctuation,
+\item hanging hyphenation,
+\item optically correct margins,
+\item various combinations thereof.
+\end{itemize}
+
+Imagine the |\marginchar| as a zero-width character which is automatically
+inserted at the beginning and end of every line. It is fixed at the margin,
+thus kerning against it will drag the first or last character in the margin.
+This is of course a gross simplification of the actual algorithm which is
+complicated by the need to deal properly with |\parindent| boxes, hanging
+indentation, |\right-| and |\leftskip|s, discretionaries, and breakpoints which
+are not taken.
+
+
+\section{Implementation}
+
+The changes to \TeX{} the program sketched above are realized in the
+change file |margin.ch|. The implementation is highly inefficient:
+Lists are traversed more often than necessary and results are not cached. But
+this implementation was possible without figuring out in detail how
+|break_width| and |cur_active_width| have to be changed to incorporate
+the |\marginchar| kerning. This is what eventually should be done.
+
+\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
+ |margin-post-web2c.ch|. In the present form, |margin.ch| can only be
+ applied \emph{after} the changes for variable letter width in |hz.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 \
+ margin.ch pre-web2c.ch \
+ tex.ch.orig post-web2c.ch \
+ hz-post-web2c.ch \
+ margin-post-web2c.ch
+\end{verbatim}
+
+ \makesignature
+
+ \bibliographystyle{plain}
+ \bibliography{etex}
+
+\end{document}
+